From bd868bb724a7374f649779e5d48650155755f8c2 Mon Sep 17 00:00:00 2001 From: nihonium Date: Thu, 4 Dec 2025 10:12:05 +0300 Subject: [PATCH] fix: reworked csrf --- api/_build/openapi.yaml | 54 ++++--------------- api/openapi.yaml | 2 + api/parameters/_index.yaml | 8 +-- api/parameters/access_token.yaml | 9 ---- api/parameters/xsrf_token_cookie.yaml | 11 ---- api/parameters/xsrf_token_header.yaml | 10 ---- api/paths/users-id-titles-id.yaml | 8 +-- api/paths/users-id.yaml | 8 ++- api/securitySchemes/_index.yaml | 11 ++++ modules/frontend/src/App.tsx | 4 ++ modules/frontend/src/api/index.ts | 3 -- .../frontend/src/api/models/accessToken.ts | 9 ---- modules/frontend/src/api/models/csrfToken.ts | 11 ---- .../src/api/models/csrfTokenHeader.ts | 10 ---- .../src/api/services/DefaultService.ts | 21 -------- .../TitleStatusControls.tsx | 10 ++-- 16 files changed, 39 insertions(+), 150 deletions(-) delete mode 100644 api/parameters/access_token.yaml delete mode 100644 api/parameters/xsrf_token_cookie.yaml delete mode 100644 api/parameters/xsrf_token_header.yaml create mode 100644 api/securitySchemes/_index.yaml delete mode 100644 modules/frontend/src/api/models/accessToken.ts delete mode 100644 modules/frontend/src/api/models/csrfToken.ts delete mode 100644 modules/frontend/src/api/models/csrfTokenHeader.ts diff --git a/api/_build/openapi.yaml b/api/_build/openapi.yaml index 225e7cd..3cbb361 100644 --- a/api/_build/openapi.yaml +++ b/api/_build/openapi.yaml @@ -150,8 +150,6 @@ paths: description: User not found '500': description: Unknown server error - security: - - JwtAuthCookies: [] patch: operationId: updateUser summary: Partially update a user account @@ -160,7 +158,6 @@ paths: Password updates must be done via the dedicated auth-service (`/auth/`). Fields not provided in the request body remain unchanged. parameters: - - $ref: '#/components/parameters/csrfTokenHeader' - name: user_id in: path description: User ID (primary key) @@ -229,7 +226,7 @@ paths: '500': description: Unknown server error security: - - JwtAuthCookies: [] + XsrfAuthHeader: [] '/users/{user_id}/titles': get: operationId: getUserTitles @@ -405,14 +402,11 @@ paths: description: User or title not found '500': description: Unknown server error - security: - - JwtAuthCookies: [] patch: operationId: updateUserTitle summary: Update a usertitle description: User updating title list of watched parameters: - - $ref: '#/components/parameters/csrfTokenHeader' - name: user_id in: path required: true @@ -455,13 +449,12 @@ paths: '500': description: Internal server error security: - - JwtAuthCookies: [] + - XsrfAuthHeader: [] delete: operationId: deleteUserTitle summary: Delete a usertitle description: User deleting title from list of watched parameters: - - $ref: '#/components/parameters/csrfTokenHeader' - name: user_id in: path required: true @@ -486,42 +479,9 @@ paths: '500': description: Internal server error security: - - JwtAuthCookies: [] + - XsrfAuthHeader: [] components: parameters: - accessToken: - name: access_token - in: cookie - required: true - schema: - type: string - format: jwt - example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.x.y - description: | - JWT access token. - csrfToken: - name: xsrf_token - in: cookie - required: true - schema: - type: string - pattern: '^[a-zA-Z0-9_-]{32,64}$' - example: abc123def456ghi789jkl012mno345pqr - description: | - Anti-CSRF token (Double Submit Cookie pattern). - Stored in non-HttpOnly cookie, readable by JavaScript. - Must be echoed in `X-XSRF-TOKEN` header for state-changing requests (POST/PUT/PATCH/DELETE). - csrfTokenHeader: - name: X-XSRF-TOKEN - in: header - required: true - schema: - type: string - pattern: '^[a-zA-Z0-9_-]{32,64}$' - description: | - Anti-CSRF token. Must match the `XSRF-TOKEN` cookie. - Required for all state-changing requests (POST/PUT/PATCH/DELETE). - example: abc123def456ghi789jkl012mno345pqr cursor: in: query name: cursor @@ -780,3 +740,11 @@ components: Review: type: object additionalProperties: true + securitySchemes: + XsrfAuthHeader: + type: apiKey + in: header + name: X-XSRF-TOKEN + description: | + Anti-CSRF token. Must match the `XSRF-TOKEN` cookie. + Required for all state-changing requests (POST/PUT/PATCH/DELETE). diff --git a/api/openapi.yaml b/api/openapi.yaml index 08a4d54..d84797f 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -23,3 +23,5 @@ components: $ref: "./parameters/_index.yaml" schemas: $ref: "./schemas/_index.yaml" + securitySchemes: + $ref: "./securitySchemes/_index.yaml" \ No newline at end of file diff --git a/api/parameters/_index.yaml b/api/parameters/_index.yaml index d2e12a8..6249e7d 100644 --- a/api/parameters/_index.yaml +++ b/api/parameters/_index.yaml @@ -1,10 +1,4 @@ cursor: $ref: "./cursor.yaml" title_sort: - $ref: "./title_sort.yaml" -accessToken: - $ref: "./access_token.yaml" -csrfToken: - $ref: "./xsrf_token_cookie.yaml" -csrfTokenHeader: - $ref: "./xsrf_token_header.yaml" \ No newline at end of file + $ref: "./title_sort.yaml" \ No newline at end of file diff --git a/api/parameters/access_token.yaml b/api/parameters/access_token.yaml deleted file mode 100644 index a7e727e..0000000 --- a/api/parameters/access_token.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: access_token -in: cookie -required: true -schema: - type: string - format: jwt -example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.x.y" -description: | - JWT access token. diff --git a/api/parameters/xsrf_token_cookie.yaml b/api/parameters/xsrf_token_cookie.yaml deleted file mode 100644 index 37041e0..0000000 --- a/api/parameters/xsrf_token_cookie.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: xsrf_token -in: cookie -required: true -schema: - type: string - pattern: "^[a-zA-Z0-9_-]{32,64}$" -example: "abc123def456ghi789jkl012mno345pqr" -description: | - Anti-CSRF token (Double Submit Cookie pattern). - Stored in non-HttpOnly cookie, readable by JavaScript. - Must be echoed in `X-XSRF-TOKEN` header for state-changing requests (POST/PUT/PATCH/DELETE). \ No newline at end of file diff --git a/api/parameters/xsrf_token_header.yaml b/api/parameters/xsrf_token_header.yaml deleted file mode 100644 index ac14dc1..0000000 --- a/api/parameters/xsrf_token_header.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: X-XSRF-TOKEN -in: header -required: true -schema: - type: string - pattern: "^[a-zA-Z0-9_-]{32,64}$" -description: | - Anti-CSRF token. Must match the `XSRF-TOKEN` cookie. - Required for all state-changing requests (POST/PUT/PATCH/DELETE). -example: "abc123def456ghi789jkl012mno345pqr" \ No newline at end of file diff --git a/api/paths/users-id-titles-id.yaml b/api/paths/users-id-titles-id.yaml index b56d07a..1da2b81 100644 --- a/api/paths/users-id-titles-id.yaml +++ b/api/paths/users-id-titles-id.yaml @@ -1,8 +1,6 @@ get: summary: Get user title operationId: getUserTitle - security: - - JwtAuthCookies: [] parameters: - in: path name: user_id @@ -37,9 +35,8 @@ patch: description: User updating title list of watched operationId: updateUserTitle security: - - JwtAuthCookies: [] + - XsrfAuthHeader: [] parameters: - - $ref: '../parameters/xsrf_token_header.yaml' - in: path name: user_id required: true @@ -87,9 +84,8 @@ delete: description: User deleting title from list of watched operationId: deleteUserTitle security: - - JwtAuthCookies: [] + - XsrfAuthHeader: [] parameters: - - $ref: '../parameters/xsrf_token_header.yaml' - in: path name: user_id required: true diff --git a/api/paths/users-id.yaml b/api/paths/users-id.yaml index abb170e..5e9e69d 100644 --- a/api/paths/users-id.yaml +++ b/api/paths/users-id.yaml @@ -1,8 +1,6 @@ get: summary: Get user info operationId: getUsersId - security: - - JwtAuthCookies: [] parameters: - in: path name: user_id @@ -30,15 +28,15 @@ get: patch: summary: Partially update a user account - security: - - JwtAuthCookies: [] description: | Update selected user profile fields (excluding password). Password updates must be done via the dedicated auth-service (`/auth/`). Fields not provided in the request body remain unchanged. operationId: updateUser + security: + XsrfAuthHeader: [] parameters: - - $ref: '../parameters/xsrf_token_header.yaml' + # - $ref: '../parameters/xsrf_token_header.yaml' - name: user_id in: path required: true diff --git a/api/securitySchemes/_index.yaml b/api/securitySchemes/_index.yaml new file mode 100644 index 0000000..ecc0ff6 --- /dev/null +++ b/api/securitySchemes/_index.yaml @@ -0,0 +1,11 @@ +# accessToken: +# $ref: "./access_token.yaml" +# csrfToken: +# $ref: "./xsrf_token_cookie.yaml" +XsrfAuthHeader: + type: apiKey + in: header + name: X-XSRF-TOKEN + description: | + Anti-CSRF token. Must match the `XSRF-TOKEN` cookie. + Required for all state-changing requests (POST/PUT/PATCH/DELETE). \ No newline at end of file diff --git a/modules/frontend/src/App.tsx b/modules/frontend/src/App.tsx index 95b59e3..5ff2b32 100644 --- a/modules/frontend/src/App.tsx +++ b/modules/frontend/src/App.tsx @@ -6,6 +6,10 @@ import TitlePage from "./pages/TitlePage/TitlePage"; import { LoginPage } from "./pages/LoginPage/LoginPage"; import { Header } from "./components/Header/Header"; +import { OpenAPI } from "./api"; + +OpenAPI.WITH_CREDENTIALS = true + const App: React.FC = () => { const username = localStorage.getItem("username") || undefined; const userId = localStorage.getItem("userId"); diff --git a/modules/frontend/src/api/index.ts b/modules/frontend/src/api/index.ts index c1e9cdc..9013fc7 100644 --- a/modules/frontend/src/api/index.ts +++ b/modules/frontend/src/api/index.ts @@ -7,9 +7,6 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise'; export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; -export type { accessToken } from './models/accessToken'; -export type { csrfToken } from './models/csrfToken'; -export type { csrfTokenHeader } from './models/csrfTokenHeader'; export type { cursor } from './models/cursor'; export type { CursorObj } from './models/CursorObj'; export type { Image } from './models/Image'; diff --git a/modules/frontend/src/api/models/accessToken.ts b/modules/frontend/src/api/models/accessToken.ts deleted file mode 100644 index adc8fb7..0000000 --- a/modules/frontend/src/api/models/accessToken.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -/** - * JWT access token. - * - */ -export type accessToken = string; diff --git a/modules/frontend/src/api/models/csrfToken.ts b/modules/frontend/src/api/models/csrfToken.ts deleted file mode 100644 index 4af805b..0000000 --- a/modules/frontend/src/api/models/csrfToken.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -/** - * Anti-CSRF token (Double Submit Cookie pattern). - * Stored in non-HttpOnly cookie, readable by JavaScript. - * Must be echoed in `X-XSRF-TOKEN` header for state-changing requests (POST/PUT/PATCH/DELETE). - * - */ -export type csrfToken = string; diff --git a/modules/frontend/src/api/models/csrfTokenHeader.ts b/modules/frontend/src/api/models/csrfTokenHeader.ts deleted file mode 100644 index 354c8a3..0000000 --- a/modules/frontend/src/api/models/csrfTokenHeader.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -/** - * Anti-CSRF token. Must match the `XSRF-TOKEN` cookie. - * Required for all state-changing requests (POST/PUT/PATCH/DELETE). - * - */ -export type csrfTokenHeader = string; diff --git a/modules/frontend/src/api/services/DefaultService.ts b/modules/frontend/src/api/services/DefaultService.ts index f3d803d..6898c46 100644 --- a/modules/frontend/src/api/services/DefaultService.ts +++ b/modules/frontend/src/api/services/DefaultService.ts @@ -135,16 +135,12 @@ export class DefaultService { * Password updates must be done via the dedicated auth-service (`/auth/`). * Fields not provided in the request body remain unchanged. * - * @param xXsrfToken Anti-CSRF token. Must match the `XSRF-TOKEN` cookie. - * Required for all state-changing requests (POST/PUT/PATCH/DELETE). - * * @param userId User ID (primary key) * @param requestBody * @returns User User updated successfully. Returns updated user representation (excluding sensitive fields). * @throws ApiError */ public static updateUser( - xXsrfToken: string, userId: number, requestBody: { /** @@ -175,9 +171,6 @@ export class DefaultService { path: { 'user_id': userId, }, - headers: { - 'X-XSRF-TOKEN': xXsrfToken, - }, body: requestBody, mediaType: 'application/json', errors: { @@ -316,9 +309,6 @@ export class DefaultService { /** * Update a usertitle * User updating title list of watched - * @param xXsrfToken Anti-CSRF token. Must match the `XSRF-TOKEN` cookie. - * Required for all state-changing requests (POST/PUT/PATCH/DELETE). - * * @param userId * @param titleId * @param requestBody @@ -326,7 +316,6 @@ export class DefaultService { * @throws ApiError */ public static updateUserTitle( - xXsrfToken: string, userId: number, titleId: number, requestBody: { @@ -341,9 +330,6 @@ export class DefaultService { 'user_id': userId, 'title_id': titleId, }, - headers: { - 'X-XSRF-TOKEN': xXsrfToken, - }, body: requestBody, mediaType: 'application/json', errors: { @@ -358,16 +344,12 @@ export class DefaultService { /** * Delete a usertitle * User deleting title from list of watched - * @param xXsrfToken Anti-CSRF token. Must match the `XSRF-TOKEN` cookie. - * Required for all state-changing requests (POST/PUT/PATCH/DELETE). - * * @param userId * @param titleId * @returns any Title successfully deleted * @throws ApiError */ public static deleteUserTitle( - xXsrfToken: string, userId: number, titleId: number, ): CancelablePromise { @@ -378,9 +360,6 @@ export class DefaultService { 'user_id': userId, 'title_id': titleId, }, - headers: { - 'X-XSRF-TOKEN': xXsrfToken, - }, errors: { 401: `Unauthorized — missing or invalid auth token`, 403: `Forbidden — user not allowed to delete title`, diff --git a/modules/frontend/src/components/TitleStatusControls/TitleStatusControls.tsx b/modules/frontend/src/components/TitleStatusControls/TitleStatusControls.tsx index 4fb535a..cc9f80d 100644 --- a/modules/frontend/src/components/TitleStatusControls/TitleStatusControls.tsx +++ b/modules/frontend/src/components/TitleStatusControls/TitleStatusControls.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { DefaultService } from "../../api"; import type { UserTitleStatus } from "../../api"; -import { useCookies } from 'react-cookie'; +// import { useCookies } from 'react-cookie'; import { ClockIcon, @@ -19,8 +19,8 @@ const STATUS_BUTTONS: { status: UserTitleStatus; icon: React.ReactNode; label: s ]; export function TitleStatusControls({ titleId }: { titleId: number }) { - const [cookies] = useCookies(['xsrf_token']); - const xsrfToken = cookies['xsrf_token'] || null; + // const [cookies] = useCookies(['xsrf_token']); + // const xsrfToken = cookies['xsrf_token'] || null; const [currentStatus, setCurrentStatus] = useState(null); const [loading, setLoading] = useState(false); @@ -46,7 +46,7 @@ export function TitleStatusControls({ titleId }: { titleId: number }) { try { // 1) Если кликнули на текущий статус — DELETE if (currentStatus === status) { - await DefaultService.deleteUserTitle(xsrfToken, userId, titleId); + await DefaultService.deleteUserTitle(userId, titleId); setCurrentStatus(null); return; } @@ -61,7 +61,7 @@ export function TitleStatusControls({ titleId }: { titleId: number }) { setCurrentStatus(added.status); } else { // уже есть запись — PATCH - const updated = await DefaultService.updateUserTitle(xsrfToken, userId, titleId, { status }); + const updated = await DefaultService.updateUserTitle(userId, titleId, { status }); setCurrentStatus(updated.status); } } finally {