Some checks failed
Deploy on push / deploy (push) Failing after 39s
### 🚨 CRITICAL Security Fixes - **🔒 Open Redirect Protection**: Добавлена строгая валидация redirect_uri против whitelist доменов - **🔒 Rate Limiting**: Защита OAuth endpoints от брутфорса (10 попыток за 5 минут на IP) - **🔒 Logout Endpoint**: Критически важный endpoint для безопасного отзыва httpOnly cookies - **🔒 Provider Validation**: Усиленная валидация OAuth провайдеров с логированием атак - **🚨 GlitchTip Alerts**: Автоматические алерты безопасности в GlitchTip при критических событиях ### 🛡️ Security Modules - **auth/oauth_security.py**: Модуль безопасности OAuth с валидацией и rate limiting + GlitchTip алерты - **auth/logout.py**: Безопасный logout с поддержкой JSON API и browser redirect - **tests/test_oauth_security.py**: Комплексные тесты безопасности (11 тестов) - **tests/test_oauth_glitchtip_alerts.py**: Тесты интеграции с GlitchTip (8 тестов) ### 🔧 OAuth Improvements - **Minimal Flow**: Упрощен до минимума - только httpOnly cookie, нет JWT в URL - **Simple Logic**: Нет error параметра = успех, максимальная простота - **DRY Refactoring**: Устранено дублирование кода в logout и валидации ### 🎯 OAuth Endpoints - **Старт**: `v3.dscrs.site/oauth/{provider}` - с rate limiting и валидацией - **Callback**: `v3.dscrs.site/oauth/{provider}/callback` - безопасный redirect_uri - **Logout**: `v3.dscrs.site/auth/logout` - отзыв httpOnly cookies - **Финализация**: `testing.discours.io/oauth?redirect_url=...` - минимальная схема ### 📊 Security Test Coverage - ✅ Open redirect attack prevention - ✅ Rate limiting protection - ✅ Provider validation - ✅ Safe fallback mechanisms - ✅ Cookie security (httpOnly + Secure + SameSite) - ✅ GlitchTip integration (8 тестов алертов) ### 📝 Documentation - Создан `docs/oauth-minimal-flow.md` - полное описание минимального flow - Обновлена документация OAuth в `docs/auth/oauth.md` - Добавлены security best practices
179 lines
5.3 KiB
JavaScript
179 lines
5.3 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* 🔍 Проверка доступности GraphQL сервера для Code Generator
|
||
*
|
||
* Проверяет доступность v3.dscrs.site/graphql и переключается на локальные схемы
|
||
* если сервер недоступен (например, в CI окружении Vercel/Netlify)
|
||
*/
|
||
|
||
import { readFileSync, writeFileSync, existsSync } from 'fs'
|
||
import { join } from 'path'
|
||
|
||
const GRAPHQL_URL = 'https://v3.dscrs.site/graphql'
|
||
const TIMEOUT = 10000 // 10 секунд
|
||
|
||
/**
|
||
* Проверяет доступность GraphQL сервера
|
||
*/
|
||
async function checkGraphQLServer() {
|
||
try {
|
||
console.log('🔍 Проверяем доступность GraphQL сервера...')
|
||
|
||
const controller = new AbortController()
|
||
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT)
|
||
|
||
const response = await fetch(GRAPHQL_URL, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'User-Agent': 'GraphQL-Codegen-Check/1.0'
|
||
},
|
||
body: JSON.stringify({
|
||
query: '{ __typename }'
|
||
}),
|
||
signal: controller.signal
|
||
})
|
||
|
||
clearTimeout(timeoutId)
|
||
|
||
if (response.ok) {
|
||
console.log('✅ GraphQL сервер доступен')
|
||
return true
|
||
} else {
|
||
console.log(`⚠️ GraphQL сервер вернул статус: ${response.status}`)
|
||
return false
|
||
}
|
||
} catch (error) {
|
||
if (error.name === 'AbortError') {
|
||
console.log('⏰ Таймаут подключения к GraphQL серверу')
|
||
} else {
|
||
console.log(`❌ Ошибка подключения к GraphQL серверу: ${error.message}`)
|
||
}
|
||
return false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Создает fallback конфигурацию с локальными схемами
|
||
*/
|
||
function createFallbackConfig() {
|
||
console.log('🔄 Создаем fallback конфигурацию с локальными схемами...')
|
||
|
||
const fallbackConfig = `import type { CodegenConfig } from '@graphql-codegen/cli'
|
||
|
||
const config: CodegenConfig = {
|
||
overwrite: true,
|
||
// 🚨 FALLBACK: Используем локальные схемы вместо удаленного сервера
|
||
schema: ['schema/*.graphql'],
|
||
documents: ['panel/graphql/queries/**/*.ts', 'panel/**/*.{ts,tsx}', '!panel/graphql/generated/**'],
|
||
generates: {
|
||
'./panel/graphql/generated/introspection.json': {
|
||
plugins: ['introspection'],
|
||
config: {
|
||
minify: true
|
||
}
|
||
},
|
||
'./panel/graphql/generated/schema.graphql': {
|
||
plugins: ['schema-ast'],
|
||
config: {
|
||
includeDirectives: false
|
||
}
|
||
},
|
||
'./panel/graphql/generated/': {
|
||
preset: 'client',
|
||
plugins: [],
|
||
presetConfig: {
|
||
gqlTagName: 'gql',
|
||
fragmentMasking: false
|
||
},
|
||
config: {
|
||
scalars: {
|
||
DateTime: 'string',
|
||
JSON: 'Record<string, any>'
|
||
},
|
||
skipTypename: false,
|
||
useTypeImports: true,
|
||
dedupeOperationSuffix: true,
|
||
dedupeFragments: true,
|
||
avoidOptionals: false,
|
||
enumsAsTypes: false
|
||
}
|
||
}
|
||
},
|
||
config: {
|
||
skipTypename: false,
|
||
useTypeImports: true,
|
||
dedupeOperationSuffix: true,
|
||
dedupeFragments: true,
|
||
avoidOptionals: false,
|
||
enumsAsTypes: false
|
||
}
|
||
}
|
||
|
||
export default config`
|
||
|
||
writeFileSync('codegen.fallback.ts', fallbackConfig)
|
||
console.log('✅ Fallback конфигурация создана: codegen.fallback.ts')
|
||
}
|
||
|
||
/**
|
||
* Проверяет наличие локальных схем
|
||
*/
|
||
function checkLocalSchemas() {
|
||
const schemaFiles = [
|
||
'schema/admin.graphql',
|
||
'schema/enum.graphql',
|
||
'schema/input.graphql',
|
||
'schema/mutation.graphql',
|
||
'schema/query.graphql',
|
||
'schema/type.graphql'
|
||
]
|
||
|
||
const missingFiles = schemaFiles.filter(file => !existsSync(file))
|
||
|
||
if (missingFiles.length > 0) {
|
||
console.log('❌ Отсутствуют локальные схемы:')
|
||
missingFiles.forEach(file => console.log(` - ${file}`))
|
||
return false
|
||
}
|
||
|
||
console.log('✅ Все локальные схемы найдены')
|
||
return true
|
||
}
|
||
|
||
/**
|
||
* Основная функция
|
||
*/
|
||
async function main() {
|
||
const isCI = process.env.CI === 'true' || process.env.VERCEL || process.env.NETLIFY
|
||
|
||
if (isCI) {
|
||
console.log('🏗️ CI окружение обнаружено, используем локальные схемы')
|
||
}
|
||
|
||
const serverAvailable = await checkGraphQLServer()
|
||
|
||
if (!serverAvailable || isCI) {
|
||
if (!checkLocalSchemas()) {
|
||
console.log('❌ Локальные схемы недоступны, сборка невозможна')
|
||
process.exit(1)
|
||
}
|
||
|
||
createFallbackConfig()
|
||
|
||
// Обновляем package.json для использования fallback конфигурации
|
||
const packageJson = JSON.parse(readFileSync('package.json', 'utf8'))
|
||
packageJson.scripts.codegen = 'graphql-codegen --config codegen.fallback.ts'
|
||
writeFileSync('package.json', JSON.stringify(packageJson, null, 2))
|
||
|
||
console.log('🔄 package.json обновлен для использования fallback конфигурации')
|
||
} else {
|
||
console.log('✅ Используем удаленный GraphQL сервер')
|
||
}
|
||
}
|
||
|
||
main().catch(error => {
|
||
console.error('💥 Критическая ошибка:', error)
|
||
process.exit(1)
|
||
})
|