diff --git a/backend/src/auth/auth.controller.ts b/backend/src/auth/auth.controller.ts index 7d50338..cbbf848 100644 --- a/backend/src/auth/auth.controller.ts +++ b/backend/src/auth/auth.controller.ts @@ -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) {} diff --git a/backend/src/group/group.controller.ts b/backend/src/group/group.controller.ts index 6a3690c..942a22c 100644 --- a/backend/src/group/group.controller.ts +++ b/backend/src/group/group.controller.ts @@ -11,7 +11,7 @@ import { UpdateGroupDto, } from './dto/group.dto'; -@ApiController('group') +@ApiController('group', { authStrategy: 'ENFORCED' }) export class GroupController { constructor(private readonly groupService: GroupService) {} diff --git a/backend/src/utils/controller.decorator.ts b/backend/src/utils/controller.decorator.ts index b6673a9..4110b50 100644 --- a/backend/src/utils/controller.decorator.ts +++ b/backend/src/utils/controller.decorator.ts @@ -1,4 +1,4 @@ -import { applyDecorators, Controller } from '@nestjs/common'; +import { applyDecorators, Controller, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiCookieAuth, @@ -8,6 +8,8 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; +import { JwtGuard } from '@/auth/guards/jwt.guard'; + import { AxiosErrorDto, ForbiddenErrorDto, @@ -15,30 +17,81 @@ import { 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, 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, - description: 'Unauthorized', - }), - ApiForbiddenResponse({ - type: AxiosErrorDto, - description: 'Forbidden', - }), - ApiBearerAuth(), - ApiCookieAuth('jwt'), - ] - : []), ); + if (authStrategy !== 'UNRESTRICTED') { + decorators.push( + ApiBearerAuth(), + ApiCookieAuth('jwt'), + ApiUnauthorizedResponse({ + type: AxiosErrorDto, + description: 'Unauthorized', + status: 401, + example: { + response: { + data: { + statusCode: 401, + error: 'Unauthorized', + message: 'Human readable error', + }, + }, + status: 401, + }, + }), + ApiForbiddenResponse({ + type: AxiosErrorDto, + 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); } diff --git a/frontend/components/client-side-profile.tsx b/frontend/components/client-side-profile.tsx index 280e8fe..7a7aec0 100644 --- a/frontend/components/client-side-profile.tsx +++ b/frontend/components/client-side-profile.tsx @@ -6,7 +6,7 @@ export function ClientSideProfile() { const { data: me, status, error } = useAuthMe({ query: { retry: false } }); if (status === 'pending') return

Loading...

; if (status === 'error' && error.status === 401) return

Client: Anonymous

; - if (status === 'error') return

Something went wrong {error.response.data.message}

; + if (status === 'error') return

Something went wrong {error.response?.data?.message}

; return

Client: {me.name}

; } diff --git a/frontend/kubb.config.bundled_ismzvejjn3f.mjs b/frontend/kubb.config.bundled_ismzvejjn3f.mjs deleted file mode 100644 index 97ad58d..0000000 --- a/frontend/kubb.config.bundled_ismzvejjn3f.mjs +++ /dev/null @@ -1,37 +0,0 @@ -// kubb.config.ts -import { defineConfig } from "@kubb/core"; -import { pluginOas } from "@kubb/plugin-oas"; -import { pluginClient } from "@kubb/swagger-client"; -import { pluginTanstackQuery } from "@kubb/swagger-tanstack-query"; -import { pluginTs } from "@kubb/swagger-ts"; -import { AxiosError } from "axios"; -var kubb_config_default = defineConfig(() => { - return { - root: ".", - input: { - path: "../openapi.yaml" - }, - output: { - clean: true, - path: "./pek-api" - }, - plugins: [ - pluginOas(), - pluginTs(), - pluginClient(), - pluginTanstackQuery({ - framework: "react", - queryOptions: { - retry: (count, error) => { - if (error instanceof AxiosError && error.response?.status === 401) return false; - return count <= 2; - } - } - }) - ] - }; -}); -export { - kubb_config_default as default -}; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsia3ViYi5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL1VzZXJzL2JhbGludC9LaXItRGV2L3Blay1pbmZpbml0eS9mcm9udGVuZC9rdWJiLmNvbmZpZy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvVXNlcnMvYmFsaW50L0tpci1EZXYvcGVrLWluZmluaXR5L2Zyb250ZW5kXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy9Vc2Vycy9iYWxpbnQvS2lyLURldi9wZWstaW5maW5pdHkvZnJvbnRlbmQva3ViYi5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICdAa3ViYi9jb3JlJztcbmltcG9ydCB7IHBsdWdpbk9hcyB9IGZyb20gJ0BrdWJiL3BsdWdpbi1vYXMnO1xuaW1wb3J0IHsgcGx1Z2luQ2xpZW50IH0gZnJvbSAnQGt1YmIvc3dhZ2dlci1jbGllbnQnO1xuaW1wb3J0IHsgcGx1Z2luVGFuc3RhY2tRdWVyeSB9IGZyb20gJ0BrdWJiL3N3YWdnZXItdGFuc3RhY2stcXVlcnknO1xuaW1wb3J0IHsgcGx1Z2luVHMgfSBmcm9tICdAa3ViYi9zd2FnZ2VyLXRzJztcbmltcG9ydCB7IEF4aW9zRXJyb3IgfSBmcm9tICdheGlvcyc7XG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZygoKSA9PiB7XG4gIHJldHVybiB7XG4gICAgcm9vdDogJy4nLFxuXG4gICAgaW5wdXQ6IHtcbiAgICAgIHBhdGg6ICcuLi9vcGVuYXBpLnlhbWwnLFxuICAgIH0sXG4gICAgb3V0cHV0OiB7XG4gICAgICBjbGVhbjogdHJ1ZSxcbiAgICAgIHBhdGg6ICcuL3Blay1hcGknLFxuICAgIH0sXG4gICAgcGx1Z2luczogW1xuICAgICAgcGx1Z2luT2FzKCksXG4gICAgICBwbHVnaW5UcygpLFxuICAgICAgcGx1Z2luQ2xpZW50KCksXG4gICAgICBwbHVnaW5UYW5zdGFja1F1ZXJ5KHtcbiAgICAgICAgZnJhbWV3b3JrOiAncmVhY3QnLFxuICAgICAgICBxdWVyeU9wdGlvbnM6IHtcbiAgICAgICAgICByZXRyeTogKGNvdW50OiBudW1iZXIsIGVycm9yOiBFcnJvcikgPT4ge1xuICAgICAgICAgICAgaWYgKGVycm9yIGluc3RhbmNlb2YgQXhpb3NFcnJvciAmJiBlcnJvci5yZXNwb25zZT8uc3RhdHVzID09PSA0MDEpIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIHJldHVybiBjb3VudCA8PSAyO1xuICAgICAgICAgIH0sXG4gICAgICAgIH0sXG4gICAgICB9KSxcbiAgICBdLFxuICB9O1xufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQStRLFNBQVMsb0JBQW9CO0FBQzVTLFNBQVMsaUJBQWlCO0FBQzFCLFNBQVMsb0JBQW9CO0FBQzdCLFNBQVMsMkJBQTJCO0FBQ3BDLFNBQVMsZ0JBQWdCO0FBQ3pCLFNBQVMsa0JBQWtCO0FBRTNCLElBQU8sc0JBQVEsYUFBYSxNQUFNO0FBQ2hDLFNBQU87QUFBQSxJQUNMLE1BQU07QUFBQSxJQUVOLE9BQU87QUFBQSxNQUNMLE1BQU07QUFBQSxJQUNSO0FBQUEsSUFDQSxRQUFRO0FBQUEsTUFDTixPQUFPO0FBQUEsTUFDUCxNQUFNO0FBQUEsSUFDUjtBQUFBLElBQ0EsU0FBUztBQUFBLE1BQ1AsVUFBVTtBQUFBLE1BQ1YsU0FBUztBQUFBLE1BQ1QsYUFBYTtBQUFBLE1BQ2Isb0JBQW9CO0FBQUEsUUFDbEIsV0FBVztBQUFBLFFBQ1gsY0FBYztBQUFBLFVBQ1osT0FBTyxDQUFDLE9BQWUsVUFBaUI7QUFDdEMsZ0JBQUksaUJBQWlCLGNBQWMsTUFBTSxVQUFVLFdBQVcsSUFBSyxRQUFPO0FBQzFFLG1CQUFPLFNBQVM7QUFBQSxVQUNsQjtBQUFBLFFBQ0Y7QUFBQSxNQUNGLENBQUM7QUFBQSxJQUNIO0FBQUEsRUFDRjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== diff --git a/frontend/package.json b/frontend/package.json index 308d3ae..c0ff32c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", @@ -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" diff --git a/openapi.yaml b/openapi.yaml index 789c30e..d2df46e 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -44,27 +44,51 @@ paths: description: "" "302": description: Redirects to the AuthSch login page. - "401": &a2 + "401": &a5 description: Unauthorized + example: &a2 + response: + data: + statusCode: 401 + error: Unauthorized + message: Human readable error + status: 401 content: application/json: schema: $ref: "#/components/schemas/AxiosErrorDto" - "403": &a3 + example: *a2 + "403": &a6 description: Forbidden + example: &a3 + response: + data: + statusCode: 403 + error: Forbidden + message: Human readable error + status: 403 content: application/json: schema: $ref: "#/components/schemas/AxiosErrorDto" - "500": &a4 + example: *a3 + "500": &a7 description: Internal Server Error + example: &a4 + response: + data: + statusCode: 500 + error: Internal Server Error + message: Human readable error + status: 500 content: application/json: schema: $ref: "#/components/schemas/AxiosErrorDto" - tags: &a5 + example: *a4 + tags: &a8 - auth - security: &a6 + security: &a9 - bearer: [] - jwt: [] /api/v4/auth/callback: @@ -80,11 +104,11 @@ paths: description: "" "302": description: Redirects to the frontend and sets cookie with JWT. - "401": *a2 - "403": *a3 - "500": *a4 - tags: *a5 - security: *a6 + "401": *a5 + "403": *a6 + "500": *a7 + tags: *a8 + security: *a9 /api/v4/auth/logout: get: operationId: AuthLogout @@ -94,11 +118,11 @@ paths: description: "" "302": description: Redirects to the frontend and clears the JWT cookie. - "401": *a2 - "403": *a3 - "500": *a4 - tags: *a5 - security: *a6 + "401": *a5 + "403": *a6 + "500": *a7 + tags: *a8 + security: *a9 /api/v4/auth/me: get: operationId: AuthMe @@ -110,11 +134,11 @@ paths: application/json: schema: $ref: "#/components/schemas/UserDto" - "401": *a2 - "403": *a3 - "500": *a4 - tags: *a5 - security: *a6 + "401": *a5 + "403": *a6 + "500": *a7 + tags: *a8 + security: *a9 /api/v4/group: post: operationId: GroupCreate @@ -132,27 +156,51 @@ paths: application/json: schema: $ref: "#/components/schemas/GroupDto" - "401": &a7 + "401": &a13 description: Unauthorized + example: &a10 + response: + data: + statusCode: 401 + error: Unauthorized + message: Human readable error + status: 401 content: application/json: schema: $ref: "#/components/schemas/AxiosErrorDto" - "403": &a8 + example: *a10 + "403": &a14 description: Forbidden + example: &a11 + response: + data: + statusCode: 403 + error: Forbidden + message: Human readable error + status: 403 content: application/json: schema: $ref: "#/components/schemas/AxiosErrorDto" - "500": &a9 + example: *a11 + "500": &a15 description: Internal Server Error + example: &a12 + response: + data: + statusCode: 500 + error: Internal Server Error + message: Human readable error + status: 500 content: application/json: schema: $ref: "#/components/schemas/AxiosErrorDto" - tags: &a10 + example: *a12 + tags: &a16 - group - security: &a11 + security: &a17 - bearer: [] - jwt: [] get: @@ -179,11 +227,11 @@ paths: type: array items: $ref: "#/components/schemas/GroupListItemDto" - "401": *a7 - "403": *a8 - "500": *a9 - tags: *a10 - security: *a11 + "401": *a13 + "403": *a14 + "500": *a15 + tags: *a16 + security: *a17 /api/v4/group/{id}: get: operationId: GroupFindOne @@ -200,11 +248,11 @@ paths: application/json: schema: $ref: "#/components/schemas/GroupDto" - "401": *a7 - "403": *a8 - "500": *a9 - tags: *a10 - security: *a11 + "401": *a13 + "403": *a14 + "500": *a15 + tags: *a16 + security: *a17 put: operationId: GroupUpdate parameters: @@ -226,11 +274,11 @@ paths: application/json: schema: $ref: "#/components/schemas/GroupDto" - "401": *a7 - "403": *a8 - "500": *a9 - tags: *a10 - security: *a11 + "401": *a13 + "403": *a14 + "500": *a15 + tags: *a16 + security: *a17 delete: operationId: GroupRemove parameters: @@ -244,11 +292,11 @@ paths: description: "" "204": description: Delete group - "401": *a7 - "403": *a8 - "500": *a9 - tags: *a10 - security: *a11 + "401": *a13 + "403": *a14 + "500": *a15 + tags: *a16 + security: *a17 info: title: PÉK API description: Profiles and Groups diff --git a/yarn.lock b/yarn.lock index 3ac44bf..9943c0c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4384,7 +4384,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:3.6.0, chokidar@npm:^3.5.2, chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": +"chokidar@npm:3.6.0, chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -5002,7 +5002,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5": version: 4.3.7 resolution: "debug@npm:4.3.7" dependencies: @@ -6704,7 +6704,6 @@ __metadata: js-cookie: "npm:^3.0.5" lucide-react: "npm:^0.441.0" next: "npm:14.2.11" - nodemon: "npm:^3.1.4" postcss: "npm:^8.4.45" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" @@ -7265,13 +7264,6 @@ __metadata: languageName: node linkType: hard -"ignore-by-default@npm:^1.0.1": - version: 1.0.1 - resolution: "ignore-by-default@npm:1.0.1" - checksum: 10c0/9ab6e70e80f7cc12735def7ecb5527cfa56ab4e1152cd64d294522827f2dcf1f6d85531241537dc3713544e88dd888f65cb3c49c7b2cddb9009087c75274e533 - languageName: node - linkType: hard - "ignore@npm:^5.0.5, ignore@npm:^5.2.0, ignore@npm:^5.2.4, ignore@npm:^5.3.1": version: 5.3.2 resolution: "ignore@npm:5.3.2" @@ -9594,26 +9586,6 @@ __metadata: languageName: node linkType: hard -"nodemon@npm:^3.1.4": - version: 3.1.4 - resolution: "nodemon@npm:3.1.4" - dependencies: - chokidar: "npm:^3.5.2" - debug: "npm:^4" - ignore-by-default: "npm:^1.0.1" - minimatch: "npm:^3.1.2" - pstree.remy: "npm:^1.1.8" - semver: "npm:^7.5.3" - simple-update-notifier: "npm:^2.0.0" - supports-color: "npm:^5.5.0" - touch: "npm:^3.1.0" - undefsafe: "npm:^2.0.5" - bin: - nodemon: bin/nodemon.js - checksum: 10c0/be2335396a4c25549f86e9c69bb57e6a21758a9649d74182a359d88b80afbe84f67a1651e293a08c6d77ecdf5c6224d02990de9de225f70d7c659021206c877f - languageName: node - linkType: hard - "nopt@npm:^7.0.0": version: 7.2.1 resolution: "nopt@npm:7.2.1" @@ -10529,13 +10501,6 @@ __metadata: languageName: node linkType: hard -"pstree.remy@npm:^1.1.8": - version: 1.1.8 - resolution: "pstree.remy@npm:1.1.8" - checksum: 10c0/30f78c88ce6393cb3f7834216cb6e282eb83c92ccb227430d4590298ab2811bc4a4745f850a27c5178e79a8f3e316591de0fec87abc19da648c2b3c6eb766d14 - languageName: node - linkType: hard - "punycode@npm:^2.1.0": version: 2.3.1 resolution: "punycode@npm:2.3.1" @@ -11360,15 +11325,6 @@ __metadata: languageName: node linkType: hard -"simple-update-notifier@npm:^2.0.0": - version: 2.0.0 - resolution: "simple-update-notifier@npm:2.0.0" - dependencies: - semver: "npm:^7.5.3" - checksum: 10c0/2a00bd03bfbcbf8a737c47ab230d7920f8bfb92d1159d421bdd194479f6d01ebc995d13fbe13d45dace23066a78a3dc6642999b4e3b38b847e6664191575b20c - languageName: node - linkType: hard - "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -11866,7 +11822,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^5.3.0, supports-color@npm:^5.5.0": +"supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" dependencies: @@ -12164,15 +12120,6 @@ __metadata: languageName: node linkType: hard -"touch@npm:^3.1.0": - version: 3.1.1 - resolution: "touch@npm:3.1.1" - bin: - nodetouch: bin/nodetouch.js - checksum: 10c0/d2e4d269a42c846a22a29065b9af0b263de58effc85a1764bb7a2e8fc4b47700e9e2fcbd7eb1f5bffbb7c73d860f93600cef282b93ddac8f0b62321cb498b36e - languageName: node - linkType: hard - "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -12588,13 +12535,6 @@ __metadata: languageName: node linkType: hard -"undefsafe@npm:^2.0.5": - version: 2.0.5 - resolution: "undefsafe@npm:2.0.5" - checksum: 10c0/96c0466a5fbf395917974a921d5d4eee67bca4b30d3a31ce7e621e0228c479cf893e783a109af6e14329b52fe2f0cb4108665fad2b87b0018c0df6ac771261d5 - languageName: node - linkType: hard - "undici-types@npm:~6.19.2": version: 6.19.8 resolution: "undici-types@npm:6.19.8"