Skip to content

Commit 3aa021b

Browse files
committed
added further restrictions on arguments
1 parent 50d7605 commit 3aa021b

File tree

2 files changed

+121
-31
lines changed

2 files changed

+121
-31
lines changed

src/app.controller.spec.ts

Lines changed: 103 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
import { AppController } from './app.controller';
22
import { Test, TestingModule } from '@nestjs/testing';
3-
import { of, Subject, Subscription, throwError } from 'rxjs';
3+
import {
4+
firstValueFrom,
5+
lastValueFrom,
6+
of,
7+
Subject,
8+
Subscription,
9+
throwError,
10+
} from 'rxjs';
411
import { HttpService } from '@nestjs/axios';
512
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
613
import { DeploymentInfo } from './deployment-info.dto';
714
import { ConfigModule } from '@nestjs/config';
815
import * as fs from 'fs';
916

1017
let onSubscription: Subscription;
11-
const lines = new Subject<string>();
18+
let lines: Subject<string>;
1219
jest.mock('tail', () => {
1320
// Mock Tail constructor
1421
return {
1522
Tail: jest.fn().mockReturnValue({
1623
on: (event, callback) => (onSubscription = lines.subscribe(callback)),
17-
unwatch: () => onSubscription.unsubscribe(),
24+
unwatch: () => {
25+
console.log('subscription', onSubscription);
26+
onSubscription.unsubscribe();
27+
},
1828
}),
1929
};
2030
});
@@ -37,6 +47,7 @@ describe('AppController', () => {
3747
};
3848

3949
beforeEach(async () => {
50+
lines = new Subject();
4051
mockWs = { write: jest.fn(), close: jest.fn() };
4152
jest.spyOn(fs, 'createWriteStream').mockReturnValue(mockWs as any);
4253
mockHttp = {
@@ -67,45 +78,107 @@ describe('AppController', () => {
6778
});
6879
});
6980

70-
it('should throw bad request exception if data has wrong format', (done) => {
71-
const invalidData = { ...deploymentData, name: 'with space' };
72-
// TODO: add an extensive list of invalid formats including attempts someone could pass to try and inject code?
81+
it('should throw bad request exception if name has wrong format', async () => {
82+
passDeployment();
83+
function testName(name: string) {
84+
return lastValueFrom(controller.deployApp({ ...deploymentData, name }));
85+
}
86+
await expect(testName('with space')).rejects.toBeInstanceOf(
87+
BadRequestException,
88+
);
89+
await expect(testName('withCapital')).resolves.toBeTruthy();
90+
await expect(testName('withSymbol?')).rejects.toBeInstanceOf(
91+
BadRequestException,
92+
);
93+
await expect(testName('withNumber123')).resolves.toBeTruthy();
94+
await expect(testName('with-dash')).resolves.toBeTruthy();
95+
await expect(testName('with_underscore')).rejects.toBeInstanceOf(
96+
BadRequestException,
97+
);
98+
});
7399

74-
controller.deployApp(invalidData).subscribe({
75-
error: (err) => {
76-
expect(err).toBeInstanceOf(BadRequestException);
77-
done();
78-
},
100+
function passDeployment() {
101+
// automatically finish deployment
102+
jest.spyOn(lines, 'subscribe').mockImplementation((fn) => {
103+
setTimeout(() => fn('DONE'));
104+
return { unsubscribe: () => undefined } as any;
79105
});
106+
}
107+
108+
it('should throw bad request exception if username has wrong format', async () => {
109+
passDeployment();
110+
function testName(username: string) {
111+
return lastValueFrom(
112+
controller.deployApp({ ...deploymentData, username }),
113+
);
114+
}
115+
116+
await expect(testName('with space')).rejects.toBeInstanceOf(
117+
BadRequestException,
118+
);
119+
await expect(testName('withCapital')).resolves.toBeTruthy();
120+
await expect(testName('withSymbol?')).rejects.toBeInstanceOf(
121+
BadRequestException,
122+
);
123+
await expect(testName('withNumber123')).resolves.toBeTruthy();
124+
await expect(testName('with-dash')).resolves.toBeTruthy();
125+
await expect(testName('with_underscore')).rejects.toBeInstanceOf(
126+
BadRequestException,
127+
);
80128
});
81129

82-
it('should throw error if ERROR is written to log', (done) => {
83-
controller.deployApp(deploymentData).subscribe({
84-
error: (err: BadRequestException) => {
85-
expect(err).toBeInstanceOf(BadRequestException);
86-
expect(err.message).toBe('my custom error');
87-
// Ensure tail is properly "unwatched"
88-
expect(onSubscription.closed).toBeTruthy();
89-
done();
90-
},
91-
});
130+
it('should throw bad request exception if email has wrong format', async () => {
131+
passDeployment();
132+
function testMail(email: string) {
133+
return lastValueFrom(controller.deployApp({ ...deploymentData, email }));
134+
}
135+
136+
await expect(testMail('testmail')).rejects.toBeInstanceOf(
137+
BadRequestException,
138+
);
139+
await expect(testMail('test@mail')).rejects.toBeInstanceOf(
140+
BadRequestException,
141+
);
142+
await expect(testMail('Test@[email protected]')).rejects.toBeInstanceOf(
143+
BadRequestException,
144+
);
145+
await expect(testMail('[email protected]')).resolves.toBeTruthy();
146+
await expect(testMail('[email protected]')).resolves.toBeTruthy();
147+
await expect(testMail('[email protected]')).resolves.toBeTruthy();
148+
await expect(testMail('[email protected]')).resolves.toBeTruthy();
149+
await expect(testMail('[email protected]')).resolves.toBeTruthy();
150+
});
92151

152+
it('should throw error if ERROR is written to log', async () => {
153+
const res = firstValueFrom(controller.deployApp(deploymentData));
93154
lines.next('some logs');
94155
lines.next('ERROR my custom error');
95-
});
96156

97-
it('should write arguments to file', (done) => {
98-
controller.deployApp(deploymentData).subscribe(() => {
99-
expect(mockWs.write).toHaveBeenCalledWith(
100-
'test-name de [email protected] test-username test-base y n',
101-
);
102-
expect(mockWs.close).toHaveBeenCalled();
157+
try {
158+
await res;
159+
} catch (err) {
160+
expect(err).toBeInstanceOf(BadRequestException);
161+
expect(err.message).toBe('my custom error');
103162
// Ensure tail is properly "unwatched"
104163
expect(onSubscription.closed).toBeTruthy();
105-
done();
106-
});
164+
return;
165+
}
166+
167+
throw new Error('No error thrown');
168+
});
169+
170+
it('should write arguments to file', () => {
171+
const res = firstValueFrom(controller.deployApp(deploymentData));
107172

108173
lines.next('DONE');
174+
175+
expect(res).resolves.toBeTruthy();
176+
expect(mockWs.write).toHaveBeenCalledWith(
177+
'test-name de [email protected] test-username test-base y n',
178+
);
179+
expect(mockWs.close).toHaveBeenCalled();
180+
// Ensure tail is properly "unwatched"
181+
expect(onSubscription.closed).toBeTruthy();
109182
});
110183

111184
it('should use the default locale if empty', (done) => {

src/app.controller.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,24 @@ export class AppController {
5151
throw new BadRequestException('No spaces allowed in arguments');
5252
}
5353

54+
if (!deploymentInfo.name.match(/^[a-zA-Z0-9\-]*$/)) {
55+
throw new BadRequestException(
56+
'Only letters, numbers and dashes are allowed in name',
57+
);
58+
}
59+
60+
if (!deploymentInfo.username.match(/^[a-zA-Z0-9\-]*$/)) {
61+
throw new BadRequestException(
62+
'Only letters, numbers and dashes are allowed in username',
63+
);
64+
}
65+
66+
// See https://regex101.com/r/lHs2R3/1
67+
const emailRegex = /^[\w\-.]+@([\w-]+\.)+[\w-]{2,}$/;
68+
if (!deploymentInfo.email.match(emailRegex)) {
69+
throw new BadRequestException('Not a valid email');
70+
}
71+
5472
const args = `${deploymentInfo.name} ${deploymentInfo.locale || 'en'} ${
5573
deploymentInfo.email
5674
} ${deploymentInfo.username} ${deploymentInfo.base} ${
@@ -80,7 +98,6 @@ export class AppController {
8098
result.complete();
8199
}
82100
});
83-
84101
return result.asObservable();
85102
}
86103
}

0 commit comments

Comments
 (0)