Skip to content

Commit b9f0ecb

Browse files
committed
feat: limit port range for Schema Apps to 80-8002
1 parent 96976ae commit b9f0ecb

File tree

5 files changed

+57
-6
lines changed

5 files changed

+57
-6
lines changed

.changeset/ten-swans-tap.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@smartthings/cli": patch
3+
"@smartthings/cli-lib": patch
4+
---
5+
6+
Limit port range for Schema Apps to 80-8002.

packages/cli/src/__tests__/lib/commands/schema-util.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ describe('webHookUrlDef', () => {
171171
expect(webHookUrlDef(false, webhookInitialValue)).toBe(generatedARNDef)
172172

173173
expect(stringDefMock).toHaveBeenCalledTimes(1)
174-
expect(stringDefMock).toHaveBeenCalledWith('Webhook URL')
174+
expect(stringDefMock).toHaveBeenCalledWith('Webhook URL', { validate: expect.any(Function) })
175175
expect(optionalDefMock).toHaveBeenCalledTimes(1)
176176
expect(optionalDefMock).toHaveBeenCalledWith(generatedStringDef, expect.any(Function), { initiallyActive: true })
177177

packages/cli/src/lib/commands/schema-util.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
stringTranslateToId,
3232
undefinedDef,
3333
updateFromUserInput,
34+
urlValidateFn,
3435
} from '@smartthings/cli-lib'
3536
import { awsHelpText } from '../aws-utils'
3637
import { chooseOrganization } from './organization-util'
@@ -55,13 +56,16 @@ export const arnDef = (name: string, inChina: boolean, initialValue?: SchemaAppR
5556
{ initiallyActive })
5657
}
5758

59+
// The SmartThings services handling Schema Apps are limited to connecting to apps in the range 80-8002.
60+
export const schemaOutURLValidate = urlValidateFn({ httpsRequired: true, minPort: 80, maxPort: 8002 })
61+
5862
export const webHookUrlDef = (inChina: boolean, initialValue?: SchemaAppRequest): InputDefinition<string | undefined> => {
5963
if (inChina) {
6064
return undefinedDef
6165
}
6266

6367
const initiallyActive = initialValue?.hostingType === 'webhook'
64-
return optionalDef(stringDef('Webhook URL'),
68+
return optionalDef(stringDef('Webhook URL', { validate: schemaOutURLValidate }),
6569
(context?: unknown[]) => (context?.[0] as Pick<SchemaAppRequest, 'hostingType'>)?.hostingType === 'webhook',
6670
{ initiallyActive })
6771
}
@@ -133,8 +137,8 @@ export const buildInputDefinition = async (
133137
appName: optionalStringDef('App Name', {
134138
default: (context?: unknown[]) => (context?.[0] as Pick<SchemaAppRequest, 'partnerName'>)?.partnerName ?? '',
135139
}),
136-
oAuthAuthorizationUrl: stringDef('OAuth Authorization URL', { validate: httpsURLValidate }),
137-
oAuthTokenUrl: stringDef('Partner OAuth Refresh Token URL', { validate: httpsURLValidate }),
140+
oAuthAuthorizationUrl: stringDef('OAuth Authorization URL', { validate: schemaOutURLValidate }),
141+
oAuthTokenUrl: stringDef('Partner OAuth Refresh Token URL', { validate: schemaOutURLValidate }),
138142
icon: optionalStringDef('Icon URL', { validate: httpsURLValidate }),
139143
icon2x: optionalStringDef('2x Icon URL', { validate: httpsURLValidate }),
140144
icon3x: optionalStringDef('3x Icon URL', { validate: httpsURLValidate }),

packages/lib/src/__tests__/validate-util.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { emailValidate, httpsURLValidate, integerValidateFn, localhostOrHTTPSValidate, stringValidateFn, urlValidate } from '../validate-util'
1+
import {
2+
emailValidate,
3+
httpsURLValidate,
4+
integerValidateFn,
5+
localhostOrHTTPSValidate,
6+
stringValidateFn,
7+
urlValidate,
8+
urlValidateFn,
9+
} from '../validate-util'
210

311

412
describe('stringValidateFn', () => {
@@ -107,6 +115,25 @@ describe('integerValidateFn', () => {
107115
})
108116
})
109117

118+
describe('urlValidateFn', () => {
119+
// We only need to get minPort and maxPort here as other bits are tested via tests of
120+
// `urlValidate`, `httpsURLValidate`, and `localhostOrHTTPSValidate` below.
121+
it.each([1, 2, 1023])('rejects %s when lower bound is 1024', (port) => {
122+
expect(urlValidateFn({ minPort: 1024 })(`https://example.com:${port}`))
123+
.toBe('Port must be between 1024 and 65535 inclusive.')
124+
})
125+
126+
it.each([10001, 10002, 65000])('rejects %s when upper bound is 10000', (port) => {
127+
expect(urlValidateFn({ maxPort: 10000 })(`https://example.com:${port}`))
128+
.toBe('Port must be between 1 and 10000 inclusive.')
129+
})
130+
131+
it.each([22, 79, 8003, 8004])('rejects %s when bounds are 80-8002', (port) => {
132+
expect(urlValidateFn({ minPort: 80, maxPort: 8002 })(`https://example.com:${port}`))
133+
.toBe('Port must be between 80 and 8002 inclusive.')
134+
})
135+
})
136+
110137
describe('urlValidate', () => {
111138
it.each([
112139
'http://example.com',

packages/lib/src/validate-util.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,27 @@ type URLValidateFnOptions = {
8989
* Required by default.
9090
*/
9191
required?: boolean
92+
93+
// default min is 1
94+
minPort?: number
95+
96+
// default max is 65535
97+
maxPort?: number
9298
}
9399

94100
const allowedHTTPHosts = ['localhost', '127.0.0.1']
95-
const urlValidateFn = (options?: URLValidateFnOptions): ValidateFunction => {
101+
export const urlValidateFn = (options?: URLValidateFnOptions): ValidateFunction => {
96102
return (input: string): true | string => {
97103
try {
98104
const url = new URL(input)
105+
const minPort = options?.minPort ?? 1
106+
const maxPort = options?.maxPort ?? 65535
107+
if (url.port) {
108+
const portNum = parseInt(url.port)
109+
if (portNum < minPort || portNum > maxPort) {
110+
return `Port must be between ${minPort} and ${maxPort} inclusive.`
111+
}
112+
}
99113
if (options?.httpsRequired) {
100114
if (options.allowLocalhostHTTP) {
101115
return url.protocol === 'https:' ||

0 commit comments

Comments
 (0)