fix: reworked csrf
All checks were successful
Build and Deploy Go App / build (push) Successful in 5m32s
Build and Deploy Go App / deploy (push) Successful in 35s

This commit is contained in:
nihonium 2025-12-04 10:12:05 +03:00
parent 475266eef6
commit bd868bb724
Signed by: nihonium
GPG key ID: 0251623741027CFC
16 changed files with 39 additions and 150 deletions

View file

@ -150,8 +150,6 @@ paths:
description: User not found description: User not found
'500': '500':
description: Unknown server error description: Unknown server error
security:
- JwtAuthCookies: []
patch: patch:
operationId: updateUser operationId: updateUser
summary: Partially update a user account summary: Partially update a user account
@ -160,7 +158,6 @@ paths:
Password updates must be done via the dedicated auth-service (`/auth/`). Password updates must be done via the dedicated auth-service (`/auth/`).
Fields not provided in the request body remain unchanged. Fields not provided in the request body remain unchanged.
parameters: parameters:
- $ref: '#/components/parameters/csrfTokenHeader'
- name: user_id - name: user_id
in: path in: path
description: User ID (primary key) description: User ID (primary key)
@ -229,7 +226,7 @@ paths:
'500': '500':
description: Unknown server error description: Unknown server error
security: security:
- JwtAuthCookies: [] XsrfAuthHeader: []
'/users/{user_id}/titles': '/users/{user_id}/titles':
get: get:
operationId: getUserTitles operationId: getUserTitles
@ -405,14 +402,11 @@ paths:
description: User or title not found description: User or title not found
'500': '500':
description: Unknown server error description: Unknown server error
security:
- JwtAuthCookies: []
patch: patch:
operationId: updateUserTitle operationId: updateUserTitle
summary: Update a usertitle summary: Update a usertitle
description: User updating title list of watched description: User updating title list of watched
parameters: parameters:
- $ref: '#/components/parameters/csrfTokenHeader'
- name: user_id - name: user_id
in: path in: path
required: true required: true
@ -455,13 +449,12 @@ paths:
'500': '500':
description: Internal server error description: Internal server error
security: security:
- JwtAuthCookies: [] - XsrfAuthHeader: []
delete: delete:
operationId: deleteUserTitle operationId: deleteUserTitle
summary: Delete a usertitle summary: Delete a usertitle
description: User deleting title from list of watched description: User deleting title from list of watched
parameters: parameters:
- $ref: '#/components/parameters/csrfTokenHeader'
- name: user_id - name: user_id
in: path in: path
required: true required: true
@ -486,42 +479,9 @@ paths:
'500': '500':
description: Internal server error description: Internal server error
security: security:
- JwtAuthCookies: [] - XsrfAuthHeader: []
components: components:
parameters: 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: cursor:
in: query in: query
name: cursor name: cursor
@ -780,3 +740,11 @@ components:
Review: Review:
type: object type: object
additionalProperties: true 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).

View file

@ -23,3 +23,5 @@ components:
$ref: "./parameters/_index.yaml" $ref: "./parameters/_index.yaml"
schemas: schemas:
$ref: "./schemas/_index.yaml" $ref: "./schemas/_index.yaml"
securitySchemes:
$ref: "./securitySchemes/_index.yaml"

View file

@ -2,9 +2,3 @@ cursor:
$ref: "./cursor.yaml" $ref: "./cursor.yaml"
title_sort: title_sort:
$ref: "./title_sort.yaml" $ref: "./title_sort.yaml"
accessToken:
$ref: "./access_token.yaml"
csrfToken:
$ref: "./xsrf_token_cookie.yaml"
csrfTokenHeader:
$ref: "./xsrf_token_header.yaml"

View file

@ -1,9 +0,0 @@
name: access_token
in: cookie
required: true
schema:
type: string
format: jwt
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.x.y"
description: |
JWT access token.

View file

@ -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).

View file

@ -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"

View file

@ -1,8 +1,6 @@
get: get:
summary: Get user title summary: Get user title
operationId: getUserTitle operationId: getUserTitle
security:
- JwtAuthCookies: []
parameters: parameters:
- in: path - in: path
name: user_id name: user_id
@ -37,9 +35,8 @@ patch:
description: User updating title list of watched description: User updating title list of watched
operationId: updateUserTitle operationId: updateUserTitle
security: security:
- JwtAuthCookies: [] - XsrfAuthHeader: []
parameters: parameters:
- $ref: '../parameters/xsrf_token_header.yaml'
- in: path - in: path
name: user_id name: user_id
required: true required: true
@ -87,9 +84,8 @@ delete:
description: User deleting title from list of watched description: User deleting title from list of watched
operationId: deleteUserTitle operationId: deleteUserTitle
security: security:
- JwtAuthCookies: [] - XsrfAuthHeader: []
parameters: parameters:
- $ref: '../parameters/xsrf_token_header.yaml'
- in: path - in: path
name: user_id name: user_id
required: true required: true

View file

@ -1,8 +1,6 @@
get: get:
summary: Get user info summary: Get user info
operationId: getUsersId operationId: getUsersId
security:
- JwtAuthCookies: []
parameters: parameters:
- in: path - in: path
name: user_id name: user_id
@ -30,15 +28,15 @@ get:
patch: patch:
summary: Partially update a user account summary: Partially update a user account
security:
- JwtAuthCookies: []
description: | description: |
Update selected user profile fields (excluding password). Update selected user profile fields (excluding password).
Password updates must be done via the dedicated auth-service (`/auth/`). Password updates must be done via the dedicated auth-service (`/auth/`).
Fields not provided in the request body remain unchanged. Fields not provided in the request body remain unchanged.
operationId: updateUser operationId: updateUser
security:
XsrfAuthHeader: []
parameters: parameters:
- $ref: '../parameters/xsrf_token_header.yaml' # - $ref: '../parameters/xsrf_token_header.yaml'
- name: user_id - name: user_id
in: path in: path
required: true required: true

View file

@ -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).

View file

@ -6,6 +6,10 @@ import TitlePage from "./pages/TitlePage/TitlePage";
import { LoginPage } from "./pages/LoginPage/LoginPage"; import { LoginPage } from "./pages/LoginPage/LoginPage";
import { Header } from "./components/Header/Header"; import { Header } from "./components/Header/Header";
import { OpenAPI } from "./api";
OpenAPI.WITH_CREDENTIALS = true
const App: React.FC = () => { const App: React.FC = () => {
const username = localStorage.getItem("username") || undefined; const username = localStorage.getItem("username") || undefined;
const userId = localStorage.getItem("userId"); const userId = localStorage.getItem("userId");

View file

@ -7,9 +7,6 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise';
export { OpenAPI } from './core/OpenAPI'; export { OpenAPI } from './core/OpenAPI';
export type { OpenAPIConfig } 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 { cursor } from './models/cursor';
export type { CursorObj } from './models/CursorObj'; export type { CursorObj } from './models/CursorObj';
export type { Image } from './models/Image'; export type { Image } from './models/Image';

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -135,16 +135,12 @@ export class DefaultService {
* Password updates must be done via the dedicated auth-service (`/auth/`). * Password updates must be done via the dedicated auth-service (`/auth/`).
* Fields not provided in the request body remain unchanged. * 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 userId User ID (primary key)
* @param requestBody * @param requestBody
* @returns User User updated successfully. Returns updated user representation (excluding sensitive fields). * @returns User User updated successfully. Returns updated user representation (excluding sensitive fields).
* @throws ApiError * @throws ApiError
*/ */
public static updateUser( public static updateUser(
xXsrfToken: string,
userId: number, userId: number,
requestBody: { requestBody: {
/** /**
@ -175,9 +171,6 @@ export class DefaultService {
path: { path: {
'user_id': userId, 'user_id': userId,
}, },
headers: {
'X-XSRF-TOKEN': xXsrfToken,
},
body: requestBody, body: requestBody,
mediaType: 'application/json', mediaType: 'application/json',
errors: { errors: {
@ -316,9 +309,6 @@ export class DefaultService {
/** /**
* Update a usertitle * Update a usertitle
* User updating title list of watched * 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 userId
* @param titleId * @param titleId
* @param requestBody * @param requestBody
@ -326,7 +316,6 @@ export class DefaultService {
* @throws ApiError * @throws ApiError
*/ */
public static updateUserTitle( public static updateUserTitle(
xXsrfToken: string,
userId: number, userId: number,
titleId: number, titleId: number,
requestBody: { requestBody: {
@ -341,9 +330,6 @@ export class DefaultService {
'user_id': userId, 'user_id': userId,
'title_id': titleId, 'title_id': titleId,
}, },
headers: {
'X-XSRF-TOKEN': xXsrfToken,
},
body: requestBody, body: requestBody,
mediaType: 'application/json', mediaType: 'application/json',
errors: { errors: {
@ -358,16 +344,12 @@ export class DefaultService {
/** /**
* Delete a usertitle * Delete a usertitle
* User deleting title from list of watched * 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 userId
* @param titleId * @param titleId
* @returns any Title successfully deleted * @returns any Title successfully deleted
* @throws ApiError * @throws ApiError
*/ */
public static deleteUserTitle( public static deleteUserTitle(
xXsrfToken: string,
userId: number, userId: number,
titleId: number, titleId: number,
): CancelablePromise<any> { ): CancelablePromise<any> {
@ -378,9 +360,6 @@ export class DefaultService {
'user_id': userId, 'user_id': userId,
'title_id': titleId, 'title_id': titleId,
}, },
headers: {
'X-XSRF-TOKEN': xXsrfToken,
},
errors: { errors: {
401: `Unauthorized — missing or invalid auth token`, 401: `Unauthorized — missing or invalid auth token`,
403: `Forbidden — user not allowed to delete title`, 403: `Forbidden — user not allowed to delete title`,

View file

@ -1,7 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { DefaultService } from "../../api"; import { DefaultService } from "../../api";
import type { UserTitleStatus } from "../../api"; import type { UserTitleStatus } from "../../api";
import { useCookies } from 'react-cookie'; // import { useCookies } from 'react-cookie';
import { import {
ClockIcon, ClockIcon,
@ -19,8 +19,8 @@ const STATUS_BUTTONS: { status: UserTitleStatus; icon: React.ReactNode; label: s
]; ];
export function TitleStatusControls({ titleId }: { titleId: number }) { export function TitleStatusControls({ titleId }: { titleId: number }) {
const [cookies] = useCookies(['xsrf_token']); // const [cookies] = useCookies(['xsrf_token']);
const xsrfToken = cookies['xsrf_token'] || null; // const xsrfToken = cookies['xsrf_token'] || null;
const [currentStatus, setCurrentStatus] = useState<UserTitleStatus | null>(null); const [currentStatus, setCurrentStatus] = useState<UserTitleStatus | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -46,7 +46,7 @@ export function TitleStatusControls({ titleId }: { titleId: number }) {
try { try {
// 1) Если кликнули на текущий статус — DELETE // 1) Если кликнули на текущий статус — DELETE
if (currentStatus === status) { if (currentStatus === status) {
await DefaultService.deleteUserTitle(xsrfToken, userId, titleId); await DefaultService.deleteUserTitle(userId, titleId);
setCurrentStatus(null); setCurrentStatus(null);
return; return;
} }
@ -61,7 +61,7 @@ export function TitleStatusControls({ titleId }: { titleId: number }) {
setCurrentStatus(added.status); setCurrentStatus(added.status);
} else { } else {
// уже есть запись — PATCH // уже есть запись — PATCH
const updated = await DefaultService.updateUserTitle(xsrfToken, userId, titleId, { status }); const updated = await DefaultService.updateUserTitle(userId, titleId, { status });
setCurrentStatus(updated.status); setCurrentStatus(updated.status);
} }
} finally { } finally {