mirror of
https://github.com/ToolJet/ToolJet
synced 2026-04-21 13:37:28 +00:00
improvements
This commit is contained in:
parent
994686a1f1
commit
e86697dbf1
7 changed files with 128 additions and 54 deletions
|
|
@ -81,3 +81,12 @@ ENABLE_ONBOARDING_QUESTIONS_FOR_ALL_SIGN_UPS=
|
|||
|
||||
#session expiry in minutes
|
||||
USER_SESSION_EXPIRY=
|
||||
|
||||
#Disable app embed feature, if true then private and public app embed is not allowed
|
||||
DISABLE_APP_EMBED=
|
||||
|
||||
# if true then private app embed is not allowed
|
||||
ENABLE_PRIVATE_APP_EMBED=
|
||||
|
||||
#Enable cors else restricted to TOOLJET_HOST
|
||||
ENABLE_CORS=
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ import { ImportExportResourcesModule } from './modules/import_export_resources/i
|
|||
import { MailerModule } from '@nestjs-modules/mailer';
|
||||
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
|
||||
import { GetConnection } from './helpers/getconnection';
|
||||
import { APP_INTERCEPTOR } from '@nestjs/core/constants';
|
||||
import { HelmetInterceptor } from './interceptors/helmet.interceptor';
|
||||
import { CustomHeadersInterceptor } from './interceptors/custom-headers.interceptors';
|
||||
|
||||
const imports = [
|
||||
ScheduleModule.forRoot(),
|
||||
|
|
@ -172,7 +175,19 @@ if (process.env.ENABLE_TOOLJET_DB === 'true') {
|
|||
@Module({
|
||||
imports,
|
||||
controllers: [AppController],
|
||||
providers: [EmailService, SeedsService, GetConnection],
|
||||
providers: [
|
||||
EmailService,
|
||||
SeedsService,
|
||||
GetConnection,
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: HelmetInterceptor,
|
||||
},
|
||||
{
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: CustomHeadersInterceptor,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule implements OnModuleInit {
|
||||
constructor() {}
|
||||
|
|
|
|||
|
|
@ -303,3 +303,7 @@ export const isValidDomain = (email: string, restrictedDomain: string): boolean
|
|||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const isHttpsEnabled = () => {
|
||||
return !!process.env.TOOLJET_HOST?.startsWith('https');
|
||||
};
|
||||
|
|
|
|||
17
server/src/interceptors/custom-headers.interceptors.ts
Normal file
17
server/src/interceptors/custom-headers.interceptors.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class CustomHeadersInterceptor implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
return next.handle().pipe(
|
||||
map((data) => {
|
||||
const response = context.switchToHttp().getResponse();
|
||||
response.setHeader('Permissions-Policy', 'geolocation=(self), camera=(), microphone=()');
|
||||
response.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
return data;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
74
server/src/interceptors/helmet.interceptor.ts
Normal file
74
server/src/interceptors/helmet.interceptor.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import * as helmet from 'helmet';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class HelmetInterceptor implements NestInterceptor {
|
||||
private readonly helmet: ReturnType<typeof helmet>;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
const host = new URL(this.configService.get('TOOLJET_HOST'));
|
||||
const domain = host.hostname;
|
||||
this.helmet = helmet({
|
||||
contentSecurityPolicy: {
|
||||
useDefaults: true,
|
||||
directives: {
|
||||
upgradeInsecureRequests: null,
|
||||
'img-src': ['*', 'data:', 'blob:'],
|
||||
'script-src': [
|
||||
'maps.googleapis.com',
|
||||
'storage.googleapis.com',
|
||||
'apis.google.com',
|
||||
'accounts.google.com',
|
||||
"'self'",
|
||||
"'unsafe-inline'",
|
||||
"'unsafe-eval'",
|
||||
'blob:',
|
||||
'https://unpkg.com/@babel/standalone@7.17.9/babel.min.js',
|
||||
'https://unpkg.com/react@16.7.0/umd/react.production.min.js',
|
||||
'https://unpkg.com/react-dom@16.7.0/umd/react-dom.production.min.js',
|
||||
'cdn.skypack.dev',
|
||||
'cdn.jsdelivr.net',
|
||||
'https://esm.sh',
|
||||
'www.googletagmanager.com',
|
||||
],
|
||||
'default-src': [
|
||||
'maps.googleapis.com',
|
||||
'storage.googleapis.com',
|
||||
'apis.google.com',
|
||||
'accounts.google.com',
|
||||
'*.sentry.io',
|
||||
"'self'",
|
||||
'blob:',
|
||||
'www.googletagmanager.com',
|
||||
],
|
||||
'connect-src': ['ws://' + domain, "'self'", '*'],
|
||||
'frame-ancestors': ['*'],
|
||||
'frame-src': ['*'],
|
||||
},
|
||||
},
|
||||
frameguard:
|
||||
this.configService.get('DISABLE_APP_EMBED') !== 'true' ||
|
||||
this.configService.get('ENABLE_PRIVATE_APP_EMBED') === 'true'
|
||||
? false
|
||||
: { action: 'deny' },
|
||||
hidePoweredBy: true,
|
||||
referrerPolicy: {
|
||||
policy: 'no-referrer',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const httpContext = context.switchToHttp();
|
||||
const request = httpContext.getRequest();
|
||||
const response = httpContext.getResponse();
|
||||
|
||||
return new Observable((subscriber) => {
|
||||
this.helmet(request, response, () => {
|
||||
next.handle().subscribe(subscriber);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import { WsAdapter } from '@nestjs/platform-ws';
|
|||
import * as cookieParser from 'cookie-parser';
|
||||
import * as compression from 'compression';
|
||||
import { AppModule } from './app.module';
|
||||
import * as helmet from 'helmet';
|
||||
import { Logger } from 'nestjs-pino';
|
||||
import { urlencoded, json } from 'express';
|
||||
import { AllExceptionsFilter } from './filters/all-exceptions-filter';
|
||||
|
|
@ -46,8 +45,6 @@ async function bootstrap() {
|
|||
abortOnError: false,
|
||||
});
|
||||
const configService = app.get<ConfigService>(ConfigService);
|
||||
const host = new URL(process.env.TOOLJET_HOST);
|
||||
const domain = host.hostname;
|
||||
|
||||
app.useLogger(app.get(Logger));
|
||||
app.useGlobalFilters(new AllExceptionsFilter(app.get(Logger)));
|
||||
|
|
@ -68,57 +65,10 @@ async function bootstrap() {
|
|||
exclude: pathsToExclude,
|
||||
});
|
||||
app.enableCors({
|
||||
origin: process.env.TOOLJET_HOST,
|
||||
origin: process.env.ENABLE_CORS === 'true' || process.env.TOOLJET_HOST,
|
||||
credentials: true,
|
||||
});
|
||||
app.use(compression());
|
||||
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: {
|
||||
useDefaults: true,
|
||||
directives: {
|
||||
upgradeInsecureRequests: null,
|
||||
'img-src': ['*', 'data:', 'blob:'],
|
||||
'script-src': [
|
||||
'maps.googleapis.com',
|
||||
'storage.googleapis.com',
|
||||
'apis.google.com',
|
||||
'accounts.google.com',
|
||||
"'self'",
|
||||
"'unsafe-inline'",
|
||||
"'unsafe-eval'",
|
||||
'blob:',
|
||||
'https://unpkg.com/@babel/standalone@7.17.9/babel.min.js',
|
||||
'https://unpkg.com/react@16.7.0/umd/react.production.min.js',
|
||||
'https://unpkg.com/react-dom@16.7.0/umd/react-dom.production.min.js',
|
||||
'cdn.skypack.dev',
|
||||
'cdn.jsdelivr.net',
|
||||
'https://esm.sh',
|
||||
'www.googletagmanager.com',
|
||||
],
|
||||
'default-src': [
|
||||
'maps.googleapis.com',
|
||||
'storage.googleapis.com',
|
||||
'apis.google.com',
|
||||
'accounts.google.com',
|
||||
'*.sentry.io',
|
||||
"'self'",
|
||||
'blob:',
|
||||
'www.googletagmanager.com',
|
||||
],
|
||||
'connect-src': ['ws://' + domain, "'self'", '*'],
|
||||
'frame-ancestors': ['*'],
|
||||
'frame-src': ['*'],
|
||||
},
|
||||
},
|
||||
frameguard: process.env.ENABLE_APP_EMBED || process.env.ENABLE_PRIVATE_APP_EMBED ? false : { action: 'deny' },
|
||||
hidePoweredBy: true,
|
||||
})
|
||||
);
|
||||
// app.use(
|
||||
// helmet.crossOriginEmbedderPolicy({}))
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(json({ limit: '50mb' }));
|
||||
app.use(urlencoded({ extended: true, limit: '50mb', parameterLimit: 1000000 }));
|
||||
|
|
@ -142,7 +92,8 @@ async function bootstrap() {
|
|||
|
||||
await app.listen(port, listen_addr, function () {
|
||||
const tooljetHost = configService.get<string>('TOOLJET_HOST');
|
||||
console.log(`Ready to use at ${tooljetHost} 🚀`);
|
||||
const subPath = configService.get<string>('SUB_PATH');
|
||||
console.log(`Ready to use at ${tooljetHost}${subPath || ''} 🚀`);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import {
|
|||
generateNextNameAndSlug,
|
||||
generateOrgInviteURL,
|
||||
isValidDomain,
|
||||
isHttpsEnabled,
|
||||
} from 'src/helpers/utils.helper';
|
||||
import {
|
||||
getUserErrorMessages,
|
||||
|
|
@ -702,7 +703,8 @@ export class AuthService {
|
|||
async forgotPassword(email: string) {
|
||||
const user = await this.usersService.findByEmail(email);
|
||||
if (!user) {
|
||||
throw new BadRequestException('Email address not found');
|
||||
// No need to throw error - To prevent Username Enumeration vulnerability
|
||||
return;
|
||||
}
|
||||
const forgotPasswordToken = uuid.v4();
|
||||
await this.usersService.updateUser(user.id, { forgotPasswordToken });
|
||||
|
|
@ -1078,6 +1080,7 @@ export class AuthService {
|
|||
if (organization) user.organizationId = organization.id;
|
||||
|
||||
const cookieOptions: CookieOptions = {
|
||||
secure: isHttpsEnabled(),
|
||||
httpOnly: true,
|
||||
sameSite: 'strict',
|
||||
maxAge: 2 * 365 * 24 * 60 * 60 * 1000, // maximum expiry 2 years
|
||||
|
|
@ -1211,6 +1214,7 @@ export class AuthService {
|
|||
|
||||
const cookieOptions: CookieOptions = {
|
||||
httpOnly: true,
|
||||
secure: isHttpsEnabled(),
|
||||
sameSite: 'strict',
|
||||
maxAge: 2 * 365 * 24 * 60 * 60 * 1000, // maximum expiry 2 years
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue