46 lines
4.2 KiB
Markdown
46 lines
4.2 KiB
Markdown
# neuroflash.com — Zitadel OIDC + /api/v1/login/jwt (HTTP-only registration)
|
||
|
||
Live 2026-10-07: customer создан полным HTTP-флоу без браузера (customer id в ответе login/jwt, `firstLogin:true`).
|
||
|
||
## Стек
|
||
|
||
- IdP: Zitadel на `https://id.neuroflash.com` (discovery: `/.well-known/openid-configuration`)
|
||
- Публичный app client_id `304685938091620422`, redirect `https://app.neuroflash.com/login/callback`
|
||
- scope: `openid email profile offline_access urn:zitadel:iam:org:projects:roles urn:zitadel:iam:org:project:id:304489704877737470:aud`
|
||
- Почта: Voidash `voidash.bond` проходит (API — см. attio-референс)
|
||
|
||
## Флоу (чистый HTTP, один cookiejar, редиректы вручную)
|
||
|
||
1. `GET /oauth/v2/authorize?client_id=…&response_type=code&redirect_uri=…&scope=…&state=…&nonce=…` → 302 на `/ui/login/register?authRequestID=<id>`
|
||
2. GET register-страницы с authRequestID → распарсить hidden `gorilla.csrf.Token`
|
||
3. POST `/ui/login/register`: `authRequestID`, `gorilla.csrf.Token`, `firstname`, `lastname`, `email`, `register-password`, `register-password-confirmation`, `register-term-confirmation=on`, `register-term-confirmation-privacy=on`
|
||
4. 6-значный код из письма → POST `/ui/login/user/init?authRequestID=<id>` (token+code)
|
||
5. POST `/ui/login/login` (authRequestID+csrf) → GET `/oauth/v2/authorize/callback?id=<authRequestID>` → 302 на `https://app.neuroflash.com/login/callback?code=…&state=…`
|
||
6. **Обмен (ключевой шаг, найден в chunk `0apql_5~16mez.js`, ф-я `loginToBackend`):**
|
||
```
|
||
POST https://app.neuroflash.com/api/v1/login/jwt
|
||
Authorization: Bearer <zitad…ken>
|
||
body: {"campaign_url":null,"preferred_pricing_id":null,"preferred_pricing_plan_id":null}
|
||
→ 200 {"firstLogin":true,"customer":{"id":<int>,"email":…},"workspaceId":…}
|
||
```
|
||
Этот POST создаёт customer-запись. Frontend дальше хранит customerId в cookie/store.
|
||
7. Все последующие API-вызовы: `Authorization: Bearer <zitad…ken>` + `x-customer-id: <customer.id>`
|
||
- без `x-customer-id` → 403 `"this is neither a valid service account nor a valid customer account"`
|
||
- **customer.id ≠ JWT sub** (backend int ~1.8M vs Zitadel snowflake ~3.9e17) — брать только из ответа login/jwt
|
||
- заголовок `x-access-token` вместо Bearer → 401 (workspace-service требует именно `Authorization: Bearer`)
|
||
|
||
## API-сервисы (база `https://app.neuroflash.com/api/`)
|
||
|
||
- `/workspace-service/v1/workspaces` — у свежего customer 0 воркспейсов (онбординг не пройден)
|
||
- `/ds-prototypes/model_selection/models`, `/ds-prototypes/content_generation/chat/completions` (OpenAI-форма)
|
||
- pricing-plan-service существует, но `GET /v1/pricing-plans` → 404 UNKNOWN_PATH (пути не разведаны)
|
||
- BFF-угадывание `/api/auth/*`, `/api/customer/*` = только UNKNOWN_PATH/404-шум — не тратить IP-квоту, читать чанки (pitfall 49)
|
||
|
||
## Легальный API-доступ (путь nf-gateway)
|
||
|
||
Service Account: Settings → Workspace → API Access → Create Service Account → client_credentials на `https://id.neuroflash.com/oauth/v2/token` (scope `openid`) + заголовок `x-workspace-id` (авто-детект: `GET /workspace-service/v1/workspaces`). Референс-шлюз: `github.com/aa27259/nf-gateway` (stdlib-only). Асимметрия model-id: каталог `claude-sonnet-5.5`, ответ генерации `anthropic/claude-sonnet-5.5` — шлюз режет префикс в обе стороны. Tool calling нативный (бэкенд = OpenRouter-proxy форма).
|
||
|
||
## Граница скоупа (важно)
|
||
|
||
Триал-фарм через сгенерированные IBAN (randomiban.dev) в Stripe SEPA Direct Debit = платёжное мошенничество — ОТКАЗ. Рекон регистрации выше легитимен; НЕ расширять его в схему fake-IBAN/триал-ротации/DE-VPN-обхода гео для платных триалов.
|