Skip to content

Commit

Permalink
Refactor ApiController decorator (#48)
Browse files Browse the repository at this point in the history
* Refactor ApiController decorator

* Error response OpenAPI examples

* AppController with home and ping routes (#50)

* AppController with home and ping routes

* Allow Bearer authorization header

* Unify unauthorized error message

* Fix build error

* Fix CI

* remove lambda api feature

* AppController with home and ping routes (#50)

* AppController with home and ping routes

* Allow Bearer authorization header

* Unify unauthorized error message

* Fix build error

* Fix CI

* remove lambda api feature

* Regenerate openapi.yaml

* Remove unused dependency
  • Loading branch information
tomitheninja authored Dec 9, 2024
1 parent 840d93b commit 1f97f76
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 167 deletions.
2 changes: 1 addition & 1 deletion backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ApiController } from '@/utils/controller.decorator';
import { AuthService } from './auth.service';
import { UserDto } from './entities/user.entity';

@ApiController('auth')
@ApiController('auth', { authStrategy: 'NOT_ENFORCED' })
export class AuthController {
constructor(private readonly authService: AuthService) {}

Expand Down
2 changes: 1 addition & 1 deletion backend/src/group/group.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
UpdateGroupDto,
} from './dto/group.dto';

@ApiController('group')
@ApiController('group', { authStrategy: 'ENFORCED' })
export class GroupController {
constructor(private readonly groupService: GroupService) {}

Expand Down
87 changes: 70 additions & 17 deletions backend/src/utils/controller.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { applyDecorators, Controller } from '@nestjs/common';
import { applyDecorators, Controller, UseGuards } from '@nestjs/common';
import {
ApiBearerAuth,
ApiCookieAuth,
Expand All @@ -8,37 +8,90 @@ import {
ApiUnauthorizedResponse,
} from '@nestjs/swagger';

import { JwtGuard } from '@/auth/guards/jwt.guard';

import {
AxiosErrorDto,
ForbiddenErrorDto,
InternalServerErrorDto,
UnauthorizedErrorDto,
} from './errors.dto';

export interface ApiControllerOptions {
/**
* Strategy for authentication
* ## options
* - 'UNRESTRICTED' - no authentication required
* - 'NOT_ENFORCED' - authentication reponse types are added, but authentication is not enforced
* - 'ENFORCED' - authentication is enforced, request is rejected if not authenticated
*/
authStrategy?: 'UNRESTRICTED' | 'NOT_ENFORCED' | 'ENFORCED';
}

export function ApiController(
name: string,
{ withAuth = true } = {},
{ authStrategy = 'ENFORCED' }: ApiControllerOptions = {},
): ClassDecorator {
return applyDecorators(
const decorators = [];
decorators.push(
Controller(name),
ApiTags(name),
ApiInternalServerErrorResponse({
type: AxiosErrorDto<InternalServerErrorDto>,
description: 'Internal Server Error',
status: 500,
example: {
response: {
data: {
statusCode: 500,
error: 'Internal Server Error',
message: 'Human readable error',
},
},
status: 500,
},
}),
...(withAuth
? [
ApiUnauthorizedResponse({
type: AxiosErrorDto<UnauthorizedErrorDto>,
description: 'Unauthorized',
}),
ApiForbiddenResponse({
type: AxiosErrorDto<ForbiddenErrorDto>,
description: 'Forbidden',
}),
ApiBearerAuth(),
ApiCookieAuth('jwt'),
]
: []),
);
if (authStrategy !== 'UNRESTRICTED') {
decorators.push(
ApiBearerAuth(),
ApiCookieAuth('jwt'),
ApiUnauthorizedResponse({
type: AxiosErrorDto<UnauthorizedErrorDto>,
description: 'Unauthorized',
status: 401,
example: {
response: {
data: {
statusCode: 401,
error: 'Unauthorized',
message: 'Human readable error',
},
},
status: 401,
},
}),
ApiForbiddenResponse({
type: AxiosErrorDto<ForbiddenErrorDto>,
description: 'Forbidden',
status: 403,
example: {
response: {
data: {
statusCode: 403,
error: 'Forbidden',
message: 'Human readable error',
},
},
status: 403,
},
}),
);
}
if (authStrategy === 'ENFORCED') {
decorators.push(UseGuards(JwtGuard));
}

return applyDecorators(...decorators);
}
2 changes: 1 addition & 1 deletion frontend/components/client-side-profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function ClientSideProfile() {
const { data: me, status, error } = useAuthMe({ query: { retry: false } });
if (status === 'pending') return <h1>Loading...</h1>;
if (status === 'error' && error.status === 401) return <h1>Client: Anonymous</h1>;
if (status === 'error') return <h1>Something went wrong {error.response.data.message}</h1>;
if (status === 'error') return <h1>Something went wrong {error.response?.data?.message}</h1>;

return <h1>Client: {me.name}</h1>;
}
37 changes: 0 additions & 37 deletions frontend/kubb.config.bundled_ismzvejjn3f.mjs

This file was deleted.

3 changes: 1 addition & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"lint:fix": "eslint --fix --ext .ts,.tsx .",
"lint": "eslint --ext .ts,.tsx .",
"generate": "npx kubb generate",
"generate:watch": "nodemon --watch ../openapi.yaml --exec \"yarn generate\""
"generate:watch": "npx kubb generate --watch"
},
"dependencies": {
"@kubb/cli": "^2.26.3",
Expand Down Expand Up @@ -45,7 +45,6 @@
"eslint": "^8.57.0",
"eslint-config-next": "14.2.11",
"eslint-plugin-react": "^7.36.1",
"nodemon": "^3.1.4",
"postcss": "^8.4.45",
"tailwindcss": "^3.4.11",
"typescript": "~5.6.2"
Expand Down
Loading

0 comments on commit 1f97f76

Please sign in to comment.