Skip to content

Commit 2cc4ca1

Browse files
authored
feat(backend): enforce uniqueness on tenant wallet address prefixes (#3695)
* feat(backend): ensure wallet address setting is unique on creation * feat: test for update setting * fix: tests
1 parent 7b996b5 commit 2cc4ca1

File tree

5 files changed

+144
-4
lines changed

5 files changed

+144
-4
lines changed

packages/backend/src/graphql/resolvers/tenant_settings.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
errorToMessage,
2828
TenantSettingError
2929
} from '../../tenants/settings/errors'
30+
import { createTenantSettings } from '../../tests/tenantSettings'
3031

3132
function createTenantedApolloClient(
3233
appContainer: TestContainer,
@@ -183,6 +184,71 @@ describe('Tenant Settings Resolvers', (): void => {
183184
)
184185
}
185186
})
187+
188+
test('errors if existing wallet address url is provided', async (): Promise<void> => {
189+
const walletAddressUrl = faker.internet.url()
190+
const existingTenant = await createTenant(deps)
191+
await createTenantSettings(deps, {
192+
tenantId: existingTenant.id,
193+
setting: [
194+
{
195+
key: TenantSettingKeys.WALLET_ADDRESS_URL.name,
196+
value: walletAddressUrl
197+
}
198+
]
199+
})
200+
201+
const newTenant = await createTenant(deps)
202+
const client = await createTenantedApolloClient(
203+
appContainer,
204+
newTenant.id
205+
)
206+
expect.assertions(2)
207+
try {
208+
await client
209+
.mutate({
210+
mutation: gql`
211+
mutation CreateTenantSettings(
212+
$input: CreateTenantSettingsInput!
213+
) {
214+
createTenantSettings(input: $input) {
215+
settings {
216+
key
217+
value
218+
}
219+
}
220+
}
221+
`,
222+
variables: {
223+
input: {
224+
settings: [
225+
{
226+
key: SchemaTenantSettingKey.WalletAddressUrl,
227+
value: walletAddressUrl
228+
}
229+
]
230+
}
231+
}
232+
})
233+
.then((query): CreateTenantSettingsMutationResponse => {
234+
if (query.data) {
235+
return query.data.createTenantSettings
236+
}
237+
throw new Error('Data was empty')
238+
})
239+
} catch (error) {
240+
expect(error).toBeInstanceOf(ApolloError)
241+
expect((error as ApolloError).graphQLErrors).toContainEqual(
242+
expect.objectContaining({
243+
message:
244+
errorToMessage[TenantSettingError.DuplicateWalletAddressUrl],
245+
extensions: expect.objectContaining({
246+
code: errorToCode[TenantSettingError.DuplicateWalletAddressUrl]
247+
})
248+
})
249+
)
250+
}
251+
})
186252
})
187253

188254
describe('Get Tenant Settings', (): void => {

packages/backend/src/open_payments/wallet_address/service.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe('Open Payments Wallet Address Service', (): void => {
107107
isOperator | tenantSettingUrl
108108
${false} | ${undefined}
109109
${true} | ${undefined}
110-
${true} | ${'https://alice.me'}
110+
${true} | ${`https://alice.me/${uuid()}`}
111111
`(
112112
'operator - $isOperator with tenantSettingUrl - $tenantSettingUrl',
113113
async ({ isOperator, tenantSettingUrl }): Promise<void> => {

packages/backend/src/tenants/settings/errors.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { GraphQLErrorCode } from '../../graphql/errors'
33
export enum TenantSettingError {
44
TenantNotFound = 'TenantNotFound',
55
UnknownError = 'UnknownError',
6-
InvalidSetting = 'InvalidSettingError'
6+
InvalidSetting = 'InvalidSettingError',
7+
DuplicateWalletAddressUrl = 'DuplicateWalletAddressUrl'
78
}
89

910
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -15,7 +16,8 @@ export const errorToCode: {
1516
} = {
1617
[TenantSettingError.InvalidSetting]: GraphQLErrorCode.BadUserInput,
1718
[TenantSettingError.TenantNotFound]: GraphQLErrorCode.NotFound,
18-
[TenantSettingError.UnknownError]: GraphQLErrorCode.InternalServerError
19+
[TenantSettingError.UnknownError]: GraphQLErrorCode.InternalServerError,
20+
[TenantSettingError.DuplicateWalletAddressUrl]: GraphQLErrorCode.BadUserInput
1921
}
2022

2123
export const errorToMessage: {
@@ -24,5 +26,7 @@ export const errorToMessage: {
2426
[TenantSettingError.TenantNotFound]: 'Tenant not found',
2527
[TenantSettingError.UnknownError]: 'Unknown error',
2628
[TenantSettingError.InvalidSetting]:
27-
'Invalid value for one or more tenant settings'
29+
'Invalid value for one or more tenant settings',
30+
[TenantSettingError.DuplicateWalletAddressUrl]:
31+
'Wallet Address Url already exists'
2832
}

packages/backend/src/tenants/settings/service.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,28 @@ describe('TenantSetting Service', (): void => {
286286
tenantSettingService.create(invalidIlpAddressSetting)
287287
).resolves.toEqual(TenantSettingError.InvalidSetting)
288288
})
289+
290+
test('cannot create setting with a non-unique wallet address url', async (): Promise<void> => {
291+
const existingTenant = await createTenant(deps)
292+
const existingSetting = [
293+
{
294+
key: TenantSettingKeys.WALLET_ADDRESS_URL.name,
295+
value: faker.internet.url()
296+
}
297+
]
298+
await createTenantSettings(deps, {
299+
tenantId: existingTenant.id,
300+
setting: existingSetting
301+
})
302+
303+
const newTenant = await createTenant(deps)
304+
await expect(
305+
tenantSettingService.create({
306+
tenantId: newTenant.id,
307+
setting: existingSetting
308+
})
309+
).resolves.toEqual(TenantSettingError.DuplicateWalletAddressUrl)
310+
})
289311
})
290312

291313
describe('get', () => {
@@ -513,6 +535,29 @@ describe('TenantSetting Service', (): void => {
513535
tenantSettingService.update(negativeOption)
514536
).resolves.toEqual(TenantSettingError.InvalidSetting)
515537
})
538+
539+
test('cannot update wallet address url to already existing value', async (): Promise<void> => {
540+
const walletAddressUrl = faker.internet.url()
541+
const existingTenant = await createTenant(deps)
542+
await createTenantSettings(deps, {
543+
tenantId: existingTenant.id,
544+
setting: [
545+
{
546+
key: TenantSettingKeys.WALLET_ADDRESS_URL.name,
547+
value: walletAddressUrl
548+
}
549+
]
550+
})
551+
const newTenant = await createTenant(deps)
552+
553+
await expect(
554+
tenantSettingService.update({
555+
tenantId: newTenant.id,
556+
key: TenantSettingKeys.WALLET_ADDRESS_URL.name,
557+
value: walletAddressUrl
558+
})
559+
).resolves.toEqual(TenantSettingError.DuplicateWalletAddressUrl)
560+
})
516561
})
517562

518563
describe('delete', (): void => {

packages/backend/src/tenants/settings/service.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,18 @@ async function updateTenantSetting(
120120
) {
121121
return TenantSettingError.InvalidSetting
122122
}
123+
124+
if (options.key === TenantSettingKeys.WALLET_ADDRESS_URL.name) {
125+
const existingSetting = await TenantSetting.query(deps.knex).findOne({
126+
key: TenantSettingKeys.WALLET_ADDRESS_URL.name,
127+
value: options.value
128+
})
129+
130+
if (existingSetting) {
131+
return TenantSettingError.DuplicateWalletAddressUrl
132+
}
133+
}
134+
123135
return TenantSetting.query(deps.knex)
124136
.patch({ value: options.value })
125137
.whereNull('deletedAt')
@@ -141,6 +153,19 @@ async function createTenantSetting(
141153
) {
142154
return TenantSettingError.InvalidSetting
143155
}
156+
157+
if (setting.key === TenantSettingKeys.WALLET_ADDRESS_URL.name) {
158+
const existingSetting = await TenantSetting.query(
159+
extra?.trx ?? deps.knex
160+
).findOne({
161+
key: TenantSettingKeys.WALLET_ADDRESS_URL.name,
162+
value: setting.value
163+
})
164+
165+
if (existingSetting) {
166+
return TenantSettingError.DuplicateWalletAddressUrl
167+
}
168+
}
144169
}
145170

146171
const dataToUpsert = options.setting

0 commit comments

Comments
 (0)