Skip to content

Commit

Permalink
Api pass key (#4006)
Browse files Browse the repository at this point in the history
* fix: throttle guard test revealed a problem

* fix: better error

* feat: adding api pass key

* feat: new guard

* fix: tests shouldn't break if env var is missing

* fix: switch to health check endpoint

* fix: no longer empty juris first

* fix: server side props calls now work

* fix: test fixes

* fix: updates per eric

* fix: test update

* fix: update for tests
  • Loading branch information
YazeedLoonat authored May 7, 2024
1 parent 552c517 commit fa41751
Show file tree
Hide file tree
Showing 61 changed files with 908 additions and 92 deletions.
2 changes: 2 additions & 0 deletions api/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,5 @@ TIME_ZONE=America/Los_Angeles
THROTTLE_TTL=3600000
# how many requests before we throttle
THROTTLE_LIMIT=100
# API passkey, requests missing this will not be alllowed to progress
API_PASS_KEY="some-key-here"
6 changes: 5 additions & 1 deletion api/prisma/seed-staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,12 @@ export const stagingSeed = async (
data: jurisdictionFactory(jurisdictionName, [UserRoleEnum.admin]),
});
// add another jurisdiction
let otherJusisName = randomNoun();
while (otherJusisName < jurisdictionName) {
otherJusisName = randomNoun();
}
const additionalJurisdiction = await prismaClient.jurisdictions.create({
data: jurisdictionFactory(randomNoun()),
data: jurisdictionFactory(otherJusisName),
});
// create admin user
await prismaClient.userAccounts.create({
Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/ami-chart.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ import { SuccessDTO } from '../dtos/shared/success.dto';
import { PermissionTypeDecorator } from '../decorators/permission-type.decorator';
import { JwtAuthGuard } from '../guards/jwt.guard';
import { PermissionGuard } from '../guards/permission.guard';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('/amiCharts')
@ApiTags('amiCharts')
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
@PermissionTypeDecorator('amiChart')
@UseGuards(ThrottleGuard, JwtAuthGuard, PermissionGuard)
@UseGuards(ApiKeyGuard, JwtAuthGuard, PermissionGuard)
@ApiExtraModels(AmiChartQueryParams)
export class AmiChartController {
constructor(private readonly AmiChartService: AmiChartService) {}
Expand Down
5 changes: 2 additions & 3 deletions api/src/controllers/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ import { PermissionAction } from '../decorators/permission-action.decorator';
import { permissionActions } from '../enums/permissions/permission-actions-enum';
import { AdminOrJurisdictionalAdminGuard } from '../guards/admin-or-jurisdiction-admin.guard';
import { AppService } from '../services/app.service';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller()
@UseGuards(ThrottleGuard)
@ApiExtraModels(SuccessDTO)
@ApiTags('root')
export class AppController {
Expand Down Expand Up @@ -55,7 +54,7 @@ export class AppController {
@ApiOkResponse({ type: SuccessDTO })
@PermissionAction(permissionActions.submit)
@UseInterceptors(ActivityLogInterceptor)
@UseGuards(OptionalAuthGuard, AdminOrJurisdictionalAdminGuard)
@UseGuards(ApiKeyGuard, OptionalAuthGuard, AdminOrJurisdictionalAdminGuard)
async clearTempFiles(): Promise<SuccessDTO> {
return await this.appService.clearTempFiles();
}
Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/application-flagged-set.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ import { mapTo } from '../utilities/mapTo';
import { OptionalAuthGuard } from '../guards/optional.guard';
import { PermissionGuard } from '../guards/permission.guard';
import { PermissionTypeDecorator } from '../decorators/permission-type.decorator';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('/applicationFlaggedSets')
@ApiExtraModels(SuccessDTO)
@ApiTags('applicationFlaggedSets')
@UseGuards(ThrottleGuard, OptionalAuthGuard, PermissionGuard)
@UseGuards(ApiKeyGuard, OptionalAuthGuard, PermissionGuard)
@PermissionTypeDecorator('applicationFlaggedSet')
@UsePipes(
new ValidationPipe({
Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/application.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import { ApplicationCsvExporterService } from '../services/application-csv-expor
import { ApplicationCsvQueryParams } from '../dtos/applications/application-csv-query-params.dto';
import { MostRecentApplicationQueryParams } from '../dtos/applications/most-recent-application-query-params.dto';
import { ExportLogInterceptor } from '../interceptors/export-log.interceptor';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('applications')
@ApiTags('applications')
Expand All @@ -60,7 +60,7 @@ import { ThrottleGuard } from '../guards/throttler.guard';
}),
)
@ApiExtraModels(IdDTO, AddressInput, BooleanInput, TextInput)
@UseGuards(ThrottleGuard, OptionalAuthGuard)
@UseGuards(ApiKeyGuard, OptionalAuthGuard)
@PermissionTypeDecorator('application')
@UseInterceptors(ActivityLogInterceptor)
export class ApplicationController {
Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/asset.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { CreatePresignedUploadMetadataResponse } from '../dtos/assets/create-pre
import { CreatePresignedUploadMetadata } from '../dtos/assets/create-presigned-upload-meta.dto';
import { AssetService } from '../services/asset.service';
import { defaultValidationPipeOptions } from '../utilities/default-validation-pipe-options';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('assets')
@ApiTags('assets')
Expand All @@ -29,7 +29,7 @@ import { ThrottleGuard } from '../guards/throttler.guard';
CreatePresignedUploadMetadataResponse,
)
@PermissionTypeDecorator('asset')
@UseGuards(ThrottleGuard, JwtAuthGuard, PermissionGuard)
@UseGuards(ApiKeyGuard, JwtAuthGuard, PermissionGuard)
export class AssetController {
constructor(private readonly assetService: AssetService) {}

Expand Down
6 changes: 3 additions & 3 deletions api/src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,22 @@ import { OptionalAuthGuard } from '../guards/optional.guard';
import { Login } from '../dtos/auth/login.dto';
import { mapTo } from '../utilities/mapTo';
import { User } from '../dtos/users/user.dto';
import { RequestSingleUseCode } from '../dtos/single-use-code/request-single-use-code.dto';
import { LoginViaSingleUseCode } from '../dtos/auth/login-single-use-code.dto';
import { SingleUseCodeAuthGuard } from '../guards/single-use-code.guard';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('auth')
@ApiTags('auth')
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
@UseGuards(ApiKeyGuard)
export class AuthController {
constructor(private readonly authService: AuthService) {}

@Post('login')
@ApiOperation({ summary: 'Login', operationId: 'login' })
@ApiOkResponse({ type: SuccessDTO })
@ApiBody({ type: Login })
@UseGuards(ThrottleGuard, MfaAuthGuard)
@UseGuards(MfaAuthGuard)
async login(
@Request() req: ExpressRequest,
@Response({ passthrough: true }) res: ExpressResponse,
Expand Down
9 changes: 7 additions & 2 deletions api/src/controllers/jurisdiction.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ import { SuccessDTO } from '../dtos/shared/success.dto';
import { PermissionTypeDecorator } from '../decorators/permission-type.decorator';
import { OptionalAuthGuard } from '../guards/optional.guard';
import { PermissionGuard } from '../guards/permission.guard';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('jurisdictions')
@ApiTags('jurisdictions')
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
@ApiExtraModels(JurisdictionCreate, JurisdictionUpdate, IdDTO)
@PermissionTypeDecorator('jurisdiction')
@UseGuards(ThrottleGuard, OptionalAuthGuard, PermissionGuard)
@UseGuards(OptionalAuthGuard, PermissionGuard)
export class JurisdictionController {
constructor(private readonly jurisdictionService: JurisdictionService) {}

Expand All @@ -51,6 +51,7 @@ export class JurisdictionController {
operationId: 'retrieve',
})
@ApiOkResponse({ type: Jurisdiction })
@UseGuards(ApiKeyGuard)
async retrieve(
@Param('jurisdictionId', new ParseUUIDPipe({ version: '4' }))
jurisdictionId: string,
Expand All @@ -64,6 +65,7 @@ export class JurisdictionController {
operationId: 'retrieveByName',
})
@ApiOkResponse({ type: Jurisdiction })
@UseGuards(ApiKeyGuard)
async retrieveByName(
@Param('jurisdictionName') jurisdictionName: string,
): Promise<Jurisdiction> {
Expand All @@ -78,6 +80,7 @@ export class JurisdictionController {
operationId: 'create',
})
@ApiOkResponse({ type: Jurisdiction })
@UseGuards(ApiKeyGuard)
async create(
@Body() jurisdiction: JurisdictionCreate,
): Promise<Jurisdiction> {
Expand All @@ -90,6 +93,7 @@ export class JurisdictionController {
operationId: 'update',
})
@ApiOkResponse({ type: Jurisdiction })
@UseGuards(ApiKeyGuard)
async update(
@Body() jurisdiction: JurisdictionUpdate,
): Promise<Jurisdiction> {
Expand All @@ -102,6 +106,7 @@ export class JurisdictionController {
operationId: 'delete',
})
@ApiOkResponse({ type: SuccessDTO })
@UseGuards(ApiKeyGuard)
async delete(@Body() dto: IdDTO): Promise<SuccessDTO> {
return await this.jurisdictionService.delete(dto.id);
}
Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/listing.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import { ListingCsvExporterService } from '../services/listing-csv-export.servic
import { ListingCsvQueryParams } from '../dtos/listings/listing-csv-query-params.dto';
import { PermissionGuard } from '../guards/permission.guard';
import { ExportLogInterceptor } from '../interceptors/export-log.interceptor';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('listings')
@ApiTags('listings')
Expand All @@ -64,7 +64,7 @@ import { ThrottleGuard } from '../guards/throttler.guard';
PaginationAllowsAllQueryParams,
IdDTO,
)
@UseGuards(ThrottleGuard, OptionalAuthGuard)
@UseGuards(ApiKeyGuard, OptionalAuthGuard)
@PermissionTypeDecorator('listing')
@ActivityLogMetadata([{ targetPropertyName: 'status', propertyPath: 'status' }])
@UseInterceptors(ActivityLogInterceptor)
Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/map-layer.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import { defaultValidationPipeOptions } from '../utilities/default-validation-pi
import { OptionalAuthGuard } from '../guards/optional.guard';
import { PermissionGuard } from '../guards/permission.guard';
import { PermissionTypeDecorator } from '../decorators/permission-type.decorator';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('/mapLayers')
@ApiTags('mapLayers')
@UseGuards(ThrottleGuard, OptionalAuthGuard, PermissionGuard)
@UseGuards(ApiKeyGuard, OptionalAuthGuard, PermissionGuard)
@PermissionTypeDecorator('mapLayers')
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
export class MapLayersController {
Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/multiselect-question.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { OptionalAuthGuard } from '../guards/optional.guard';
import { PermissionGuard } from '../guards/permission.guard';
import { AdminOrJurisdictionalAdminGuard } from '../guards/admin-or-jurisdiction-admin.guard';
import { ActivityLogInterceptor } from '../interceptors/activity-log.interceptor';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('multiselectQuestions')
@ApiTags('multiselectQuestions')
Expand All @@ -47,7 +47,7 @@ import { ThrottleGuard } from '../guards/throttler.guard';
IdDTO,
)
@PermissionTypeDecorator('multiselectQuestion')
@UseGuards(ThrottleGuard, OptionalAuthGuard, PermissionGuard)
@UseGuards(ApiKeyGuard, OptionalAuthGuard, PermissionGuard)
export class MultiselectQuestionController {
constructor(
private readonly multiselectQuestionService: MultiselectQuestionService,
Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/reserved-community-type.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ import { SuccessDTO } from '../dtos/shared/success.dto';
import { PermissionTypeDecorator } from '../decorators/permission-type.decorator';
import { JwtAuthGuard } from '../guards/jwt.guard';
import { PermissionGuard } from '../guards/permission.guard';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('reservedCommunityTypes')
@ApiTags('reservedCommunityTypes')
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
@ApiExtraModels(ReservedCommunityTypeQueryParams)
@PermissionTypeDecorator('reservedCommunityType')
@UseGuards(ThrottleGuard, JwtAuthGuard, PermissionGuard)
@UseGuards(ApiKeyGuard, JwtAuthGuard, PermissionGuard)
export class ReservedCommunityTypeController {
constructor(
private readonly ReservedCommunityTypeService: ReservedCommunityTypeService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ import { SuccessDTO } from '../dtos/shared/success.dto';
import { PermissionTypeDecorator } from '../decorators/permission-type.decorator';
import { JwtAuthGuard } from '../guards/jwt.guard';
import { PermissionGuard } from '../guards/permission.guard';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('unitAccessibilityPriorityTypes')
@ApiTags('unitAccessibilityPriorityTypes')
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
@ApiExtraModels(IdDTO)
@PermissionTypeDecorator('unitAccessibilityPriorityType')
@UseGuards(ThrottleGuard, JwtAuthGuard, PermissionGuard)
@UseGuards(ApiKeyGuard, JwtAuthGuard, PermissionGuard)
export class UnitAccessibilityPriorityTypeController {
constructor(
private readonly unitAccessibilityPriorityTypeService: UnitAccessibilityPriorityTypeService,
Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/unit-rent-type.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ import { SuccessDTO } from '../dtos/shared/success.dto';
import { PermissionTypeDecorator } from '../decorators/permission-type.decorator';
import { JwtAuthGuard } from '../guards/jwt.guard';
import { PermissionGuard } from '../guards/permission.guard';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('unitRentTypes')
@ApiTags('unitRentTypes')
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
@ApiExtraModels(UnitRentTypeCreate, UnitRentTypeUpdate, IdDTO)
@PermissionTypeDecorator('unitRentType')
@UseGuards(ThrottleGuard, JwtAuthGuard, PermissionGuard)
@UseGuards(ApiKeyGuard, JwtAuthGuard, PermissionGuard)
export class UnitRentTypeController {
constructor(private readonly unitRentTypeService: UnitRentTypeService) {}

Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/unit-type.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import { SuccessDTO } from '../dtos/shared/success.dto';
import { PermissionTypeDecorator } from '../decorators/permission-type.decorator';
import { JwtAuthGuard } from '../guards/jwt.guard';
import { PermissionGuard } from '../guards/permission.guard';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('unitTypes')
@ApiTags('unitTypes')
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
@PermissionTypeDecorator('unitType')
@UseGuards(ThrottleGuard, JwtAuthGuard, PermissionGuard)
@UseGuards(ApiKeyGuard, JwtAuthGuard, PermissionGuard)
export class UnitTypeController {
constructor(private readonly unitTypeService: UnitTypeService) {}

Expand Down
4 changes: 2 additions & 2 deletions api/src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ import { UserFilterParams } from '../dtos/users/user-filter-params.dto';
import { UserCsvExporterService } from '../services/user-csv-export.service';
import { ExportLogInterceptor } from '../interceptors/export-log.interceptor';
import { RequestSingleUseCode } from '../dtos/single-use-code/request-single-use-code.dto';
import { ThrottleGuard } from '../guards/throttler.guard';
import { ApiKeyGuard } from '../guards/api-key.guard';

@Controller('user')
@UseGuards(ThrottleGuard)
@ApiTags('user')
@PermissionTypeDecorator('user')
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
@ApiExtraModels(IdDTO, EmailAndAppUrl)
@UseGuards(ApiKeyGuard)
export class UserController {
constructor(
private readonly userService: UserService,
Expand Down
22 changes: 22 additions & 0 deletions api/src/guards/api-key.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';

@Injectable()
export class ApiKeyGuard implements CanActivate {
canActivate(context: ExecutionContext) {
const req = context.switchToHttp().getRequest();

if (
process.env.API_PASS_KEY &&
(!req.headers.passkey || req.headers.passkey !== process.env.API_PASS_KEY)
) {
throw new UnauthorizedException('Traffic not from a known source');
}

return true;
}
}
2 changes: 1 addition & 1 deletion api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ConfigService } from '@nestjs/config';
import cookieParser from 'cookie-parser';
import compression from 'compression';
import { json } from 'express';
import { AppModule } from './modules/app.module';
import { CustomExceptionFilter } from './utilities/custom-exception-filter';
import { json } from 'express';

async function bootstrap() {
const app = await NestFactory.create(AppModule, {
Expand Down
4 changes: 3 additions & 1 deletion api/src/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@ export class AuthService {
}
}

const singleUseCode = generateSingleUseCode();
const singleUseCode = generateSingleUseCode(
Number(process.env.MFA_CODE_LENGTH),
);
await this.prisma.userAccounts.update({
data: {
singleUseCode,
Expand Down
3 changes: 2 additions & 1 deletion api/src/services/email.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ export class EmailService {
console.error(
`Error sending email to: ${
isMultipleRecipients ? to.toString() : to
}! Error body: ${errBody}`,
}! Error body:`,
errBody,
);
if (retry > 0) {
void this.send(to, from, subject, body, retry - 1);
Expand Down
4 changes: 3 additions & 1 deletion api/src/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,9 @@ export class UserService {
);
}

const singleUseCode = generateSingleUseCode();
const singleUseCode = generateSingleUseCode(
Number(process.env.MFA_CODE_LENGTH),
);
await this.prisma.userAccounts.update({
data: {
singleUseCode,
Expand Down
Loading

0 comments on commit fa41751

Please sign in to comment.