@torrow/sdk-auth 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/index.d.ts +308 -115
- package/dist/index.js +315 -75
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/{types-LCuPy3dY.d.ts → types-C4e90uOw.d.ts} +5 -10
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ flowchart TD
|
|
|
13
13
|
need[Нужен клиент] --> kind{Сценарий}
|
|
14
14
|
kind -->|restore| connect["TorrowAuth.connect"]
|
|
15
15
|
kind -->|OTP login| login["TorrowAuth.loginWithOtp"]
|
|
16
|
+
kind -->|раздельные OTP-запросы| flow["TorrowAuth.createOtpFlow: start / complete"]
|
|
16
17
|
kind -->|Telegram deep-link| link["TorrowAuth.loginWithLink"]
|
|
17
18
|
kind -->|токены от хоста| fromTok["TorrowAuth.fromTokens"]
|
|
18
19
|
kind -->|без входа| anon["TorrowAuth.anonymous"]
|
|
@@ -27,6 +28,123 @@ flowchart TD
|
|
|
27
28
|
|
|
28
29
|
## Целевой клиентский код
|
|
29
30
|
|
|
31
|
+
### Пошаговый OTP без пользовательской сессии
|
|
32
|
+
|
|
33
|
+
`TorrowAuth.createOtpFlow` выполняет один `sendCode` на start и один `requestToken` на complete. Он не хранит challenge или токены, не требует TokenStorage/callbacks, не делает polling, refresh, retry или revoke. Для полного входа с callbacks и сохранением используйте `loginWithOtp`.
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
sequenceDiagram
|
|
37
|
+
actor User as Пользователь
|
|
38
|
+
participant App as Приложение
|
|
39
|
+
participant Flow as OtpFlow (@torrow/sdk-auth)
|
|
40
|
+
participant API as Torrow API
|
|
41
|
+
|
|
42
|
+
App->>Flow: createOtpFlow(options)
|
|
43
|
+
App->>Flow: start({ account, ... }, { signal })
|
|
44
|
+
Flow->>API: POST /auth/sendcode (Basic приложения)
|
|
45
|
+
API-->>Flow: SendCodeResp
|
|
46
|
+
Flow-->>App: OtpChallenge
|
|
47
|
+
Note over App,Flow: Classic — AwaitingCode, Mobile ID — Pending.<br/>Challenge не содержит токенов или OTP.
|
|
48
|
+
App->>App: Защитить и перенести challenge через JSON
|
|
49
|
+
Note over App,Flow: complete можно вызвать на новом экземпляре flow.<br/>deviceInfo, scopes и splitTokens сохраняет приложение.
|
|
50
|
+
|
|
51
|
+
loop Пока нет TokenReady / Failed и challenge не просрочен
|
|
52
|
+
alt challenge.phase = AwaitingCode
|
|
53
|
+
App->>User: Запросить OTP / SMS-код
|
|
54
|
+
User-->>App: code
|
|
55
|
+
App->>Flow: complete({ challenge, code }, { signal })
|
|
56
|
+
else challenge.phase = Pending
|
|
57
|
+
App->>App: При повторном polling дождаться интервала
|
|
58
|
+
App->>Flow: complete({ challenge }, { signal })
|
|
59
|
+
end
|
|
60
|
+
Note right of Flow: Проверить challenge до HTTP.<br/>Pending — wire code пустой.
|
|
61
|
+
Flow->>API: POST /auth/token (Basic приложения)
|
|
62
|
+
API-->>Flow: TokenResp
|
|
63
|
+
alt status = Pending
|
|
64
|
+
Flow-->>App: Pending + challenge (phase = Pending)<br/>+ cleanup, если есть refresh
|
|
65
|
+
else status = AwaitingSmsCode
|
|
66
|
+
Flow-->>App: SMS fallback той же Mobile ID сессии<br/>AwaitingSmsCode + challenge<br/>(новая phase = AwaitingCode, usedMethod = Sms)<br/>+ cleanup, если есть refresh
|
|
67
|
+
else status = TokenReady
|
|
68
|
+
Flow-->>App: TokenReady + tokens
|
|
69
|
+
App->>App: Использовать токены и решить вопрос хранения / cleanup
|
|
70
|
+
else status = Failed
|
|
71
|
+
Flow-->>App: Failed + cleanup, если есть refresh
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
Note over App,Flow: expiresAtMs не продлевается между шагами.<br/>Ошибки передаются приложению, скрытых повторов нет.<br/>Cleanup выполняет приложение с независимым AbortSignal.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { TorrowAuth, OtpFlowError, type OtpChallenge } from "@torrow/sdk-auth";
|
|
80
|
+
import { TokenStatusValues } from "@torrow/sdk-client";
|
|
81
|
+
|
|
82
|
+
const options = {
|
|
83
|
+
deviceInfo: { deviceId: "my-app-device", deviceType: "Desktop" as const },
|
|
84
|
+
scopes: ["user_api" as const],
|
|
85
|
+
splitTokens: false,
|
|
86
|
+
};
|
|
87
|
+
const challenge = await TorrowAuth.createOtpFlow(options).start({
|
|
88
|
+
account: { accountType: "Email", accountName: "user@example.com" },
|
|
89
|
+
});
|
|
90
|
+
// Между запросами приложение защищает целостность этого JSON.
|
|
91
|
+
const continuation: OtpChallenge = JSON.parse(JSON.stringify(challenge));
|
|
92
|
+
try {
|
|
93
|
+
const result = await TorrowAuth.createOtpFlow(options).complete(
|
|
94
|
+
{ challenge: continuation, code: "123456" },
|
|
95
|
+
{ signal: AbortSignal.timeout(15000) },
|
|
96
|
+
);
|
|
97
|
+
if (result.status === TokenStatusValues.TokenReady) {
|
|
98
|
+
// Использовать result.tokens.accessToken в рамках текущего запроса.
|
|
99
|
+
// Сохранение/cleanup result.tokens.refreshToken определяет приложение.
|
|
100
|
+
} else {
|
|
101
|
+
// result.cleanup?.refreshToken доступен для отдельного cleanup.
|
|
102
|
+
if ("challenge" in result) {
|
|
103
|
+
// Защитить result.challenge для следующего шага.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error instanceof OtpFlowError) {
|
|
108
|
+
// error.code — безопасная причина; error.cleanup?.refreshToken — секрет для cleanup.
|
|
109
|
+
// Cleanup выполняет приложение с независимым AbortSignal.
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Значение | Действие приложения |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `challenge.phase: AwaitingCode` | Для следующего шага требуется непустой код. Это первоначальный ввод при обычном OTP либо ввод SMS-кода после fallback в Mobile ID-сценарии. |
|
|
118
|
+
| `challenge.phase: Pending` | Продолжается ожидание результата Mobile ID. Следующий complete вызывается без кода; wire code будет пустым. |
|
|
119
|
+
| `result.status: Pending` | Сервер ещё не завершил аутентификацию. SDK возвращает challenge с фазой Pending; приложение планирует следующий запрос без кода. Возможен возврат к Pending после отправки SMS-кода. |
|
|
120
|
+
| `result.status: AwaitingSmsCode` | Сервер перевёл Mobile ID-сценарий на SMS fallback, поскольку аутентификацию через Mobile ID не удалось завершить. SDK возвращает challenge с новой фазой AwaitingCode и методом Sms; приложение запрашивает SMS-код и передаёт его в следующий complete. |
|
|
121
|
+
| `result.status: Failed` | Текущая попытка аутентификации завершилась отказом. Challenge для продолжения не возвращается; новый вход требует отдельного start. |
|
|
122
|
+
| `result.status: TokenReady` | Аутентификация завершена, токены получены и проверены SDK. Продолжение не требуется; SDK не сохраняет токены. |
|
|
123
|
+
|
|
124
|
+
`result.status` отражает серверный `TokenStatus`, а `challenge.phase` определяет следующий шаг приложения. Переход при fallback: ожидание Mobile ID (`Pending`) → серверный `AwaitingSmsCode` → ввод SMS-кода (новая фаза `AwaitingCode`, метод `Sms`). Это продолжение той же auth-сессии с новым способом подтверждения, а не продолжение фазы AwaitingCode: sessionId и deadline сохраняются, фаза меняется. При обычном Email/SMS/FlashCall/TelegramGateway-входе `start` сразу возвращает `AwaitingCode` с фактическим `usedMethod`: ввод кода требуется до первого `complete`. Статуса `AwaitingEmailCode` в текущем API нет. Предпочтительный метод `preferredMethod` является пожеланием; фактический метод определяет сервер в `usedMethod`.
|
|
125
|
+
|
|
126
|
+
Опции flow: `baseUrl`, `fetch`, `defaultHeaders`, `userAgent`, `appCredentials`, обязательный `deviceInfo`, `scopes`, `splitTokens`. SDK-default Basic и scopes такие же, как в сессии; неуказанный splitTokens не отправляется. Start принимает язык (default ru), регион (70), captcha/type, invite session и preferred/supported methods. Email/UnknownMethod поддерживается; неизвестный метод отклоняется.
|
|
127
|
+
|
|
128
|
+
**Когда использовать `splitTokens`.** Это параметр `POST /api/v1/auth/token`, который обрабатывает сервер: SDK передаёт флаг и возвращает полученные поля токенов без самостоятельного разделения или восстановления.
|
|
129
|
+
|
|
130
|
+
| Значение | Поведение сервера и сценарий |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `false` | Возвращает полные access/refresh в JSON и удаляет cookies ранее разделённых токенов. Используйте для Node, MCP и приложений, которым нужны полные токены для API-вызовов, выпуска интеграционного ключа и cleanup. |
|
|
133
|
+
| `true` | При наличии access token разделяет access/refresh: одну часть каждого токена помещает в HttpOnly cookies, другую возвращает в JSON. Используйте только для браузерного приложения, специально настроенного на этот режим авторизации. При Mobile ID без готовых токенов разделение пропускается. |
|
|
134
|
+
| Не задано | Flow не отправляет поле. В текущей реализации шлюза отсутствующий boolean имеет значение `false`; для явно выбранного режима полных токенов задавайте `false`. |
|
|
135
|
+
|
|
136
|
+
В режиме `true` поля `result.tokens` содержат части токенов, а не самостоятельные полные Bearer/refresh-токены. Последующие запросы должны передавать необходимые части и cookies для восстановления токенов шлюзом. Приложение обеспечивает работу с cookies, включая `credentials` у `fetch` и разрешения CORS при запросах между origin. SDK не добавляет cookie jar или управление cookies; стандартный Node `fetch` не сохраняет и не передаёт их автоматически между запросами. Поэтому Node-примеры задают `splitTokens: false`. Тесты SDK проверяют передачу обоих значений в HTTP-тело, а не серверное разделение или браузерную работу cookies.
|
|
137
|
+
|
|
138
|
+
Challenge: `sessionId`, `accountType`, `usedMethod`, `phase`, `expiresAtMs` и проверенные подсказки `codeLength`, `pollIntervalSeconds`, `attemptsRemaining`, `nextSendAtMs`, `availableMethods`. Даты — Unix milliseconds, пригодные для JSON. Токенов/OTP в challenge нет. Проверка структуры SDK не заменяет проверку целостности приложением. Между шагами приложение сохраняет тот же deviceInfo, scopes и splitTokens; flow не связывает их криптографически с challenge.
|
|
139
|
+
|
|
140
|
+
Срок отсчитывается от начала start: серверный sessionLifeTimeInSeconds либо локальные 180 секунд при отсутствии/null. Некорректный TTL отклоняется. Локальный срок не гарантирует действие сессии на сервере и не продлевается. Ограничение 600 секунд относится к MCP. Polling hint default — 3 секунды. Некорректные необязательные подсказки пропускаются; длина кода — корректная серверная либо Email 6, Phone/SMS fallback 4. Число попыток не уменьшается самостоятельно.
|
|
141
|
+
|
|
142
|
+
Перед проверкой token-ответа нормализуются существующие aliases: AccessToken/access_token, RefreshToken/refresh_token, Expiration/expires_in, Status. Приоритет: имя OpenAPI → PascalCase → snake_case по первому значению, отличному от null/undefined. Flow требует явный известный status и непустой access token при TokenReady. Старые методы сохраняют ответы без status, только с refresh token и прежние callback-поля через внутреннюю legacy-адаптацию общей реализации.
|
|
143
|
+
|
|
144
|
+
`OtpFlowError.code`: InvalidArguments, InvalidChallenge, InvalidResponse, UnsupportedMethod. Getter `error.cleanup` передаёт обнаруженный refresh даже при повреждённом ответе; он не включён в обычную сериализацию/диагностику ошибки. Валидные незавершённые/Failed ответы передают refresh через result.cleanup. HTTP-ошибки остаются TorrowHttpError, сеть и отмена пробрасываются. Потеря ответа не разрешает слепой повтор. Signal передаётся до fetch на обоих шагах; некорректный/просроченный challenge отклоняется до upstream.
|
|
145
|
+
|
|
146
|
+
Примеры: auth-otp-flow.ts (JSON и новый flow), auth-otp-flow-mobile-id.ts (polling приложения и SMS fallback). Выпуск интеграционного ключа, подтверждение identity и cleanup выполняются приложением.
|
|
147
|
+
|
|
30
148
|
Общая база `{ tokenStorage, deviceInfo }` (+ опционально `appCredentials`) у режимов с сессией.
|
|
31
149
|
Без `appCredentials` — **`DEFAULT_APP_CREDENTIALS`** (`sdk.torrow.net`). Своя пара перекрывает дефолт.
|
|
32
150
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,51 +1,93 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
3
|
-
import { TorrowClient, DeviceInfo, TokenResp, AuthScope,
|
|
1
|
+
import { A as AppCredentials, T as TokenSessionOptions, a as TokenStorage, b as TokenSessionStatus, c as TokenSessionStatusListener, d as AuthTokenSnapshot } from './types-C4e90uOw.js';
|
|
2
|
+
export { R as RefreshFailureAction } from './types-C4e90uOw.js';
|
|
3
|
+
import { SendCodeAccountType, VerificationMethod, TorrowClient, DeviceInfo, TokenResp, AuthScope, AuthCaptchaType, AccountType, TokenStatusValues } from '@torrow/sdk-client';
|
|
4
|
+
|
|
5
|
+
/** Defaults и преобразование credentials приложения в HTTP Basic. */
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
|
-
*
|
|
8
|
+
* Дефолтные app credentials SDK для `/api/v1/auth/*`, если {@link TokenSessionOptions.appCredentials}
|
|
9
|
+
* не передан. Своё приложение может переопределить явной парой.
|
|
7
10
|
*
|
|
8
|
-
*
|
|
9
|
-
* Для Node-примеров обычно {@link FileTokenStorage}; для браузера —
|
|
10
|
-
* {@link LocalStorageTokenStorage}.
|
|
11
|
+
* Публикуется в npm `@torrow/sdk-auth` (inspectable); свой prod-secret — только override / BFF.
|
|
11
12
|
*/
|
|
12
|
-
|
|
13
|
-
/** In-memory хранилище токенов сессии. */
|
|
14
|
-
declare class MemoryTokenStorage implements TokenStorage {
|
|
15
|
-
private snapshot;
|
|
16
|
-
constructor(initial?: AuthTokenSnapshot);
|
|
17
|
-
load(): Promise<AuthTokenSnapshot>;
|
|
18
|
-
save(snapshot: AuthTokenSnapshot): Promise<void>;
|
|
19
|
-
}
|
|
13
|
+
declare const DEFAULT_APP_CREDENTIALS: AppCredentials;
|
|
20
14
|
|
|
21
15
|
/**
|
|
22
|
-
*
|
|
16
|
+
* Внутренний оркестратор OTP-логина (не публичный export целиком; типы UI — да).
|
|
23
17
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
18
|
+
* **`sendCode`** → ветка по **`usedMethod`** (Mobile ID poll | classic код) →
|
|
19
|
+
* **`TokenResp`**. UI снаружи: **`onSendCode`** + **`getCode`**.
|
|
20
|
+
* IMAP / stdin / env — в app / `examples/node/_shared`, не здесь.
|
|
21
|
+
*
|
|
22
|
+
* Вызывается из `TorrowAuth.loginWithOtp`; Mobile ID — `mobile-id.ts`.
|
|
26
23
|
*/
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Аргумент **`onSendCode`**: снимок **после** успешного `sendCode` (в основном поля **`SendCodeResp`**).
|
|
27
|
+
* Не путать с телом запроса: в `sendCode` уходят `supportedMethods` / `preferredMethod`
|
|
28
|
+
* (см. {@link RunOtpLoginOptions}); здесь — что сервер **выбрал** и что ещё доступно.
|
|
29
|
+
*
|
|
30
|
+
* Типичный UI: по `usedMethod` показать «ждём SMS» / «код на email» / «подтвердите push»;
|
|
31
|
+
* по `codeLength` — маску поля ввода.
|
|
32
|
+
*/
|
|
33
|
+
type OtpSendCodeResult = {
|
|
34
|
+
/** Аккаунт из `SendCodeReq` (телефон / email). */
|
|
35
|
+
accountName: string;
|
|
36
|
+
/** `Phone` \| `Email` из запроса (не путать с `usedMethod`). */
|
|
37
|
+
accountType: SendCodeAccountType;
|
|
38
|
+
/** Id auth-сессии для последующего `requestToken` / Mobile ID poll. */
|
|
39
|
+
sessionId: string;
|
|
40
|
+
/**
|
|
41
|
+
* Фактический канал из **`SendCodeResp.usedMethod`** — ради этого и нужен `onSendCode`.
|
|
42
|
+
* Sms / Email / FlashCall / TelegramGateway / MtsMobileId / …
|
|
43
|
+
*/
|
|
44
|
+
usedMethod?: VerificationMethod;
|
|
45
|
+
/** Длина кода для input/маски; у Mobile ID часто `0` (keypad не нужен). */
|
|
46
|
+
codeLength?: number;
|
|
47
|
+
/** Интервал poll при Mobile ID (`SendCodeResp.pollIntervalSeconds`). */
|
|
48
|
+
pollIntervalSeconds?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Из **`SendCodeResp`** (не тело `sendCode`): методы, доступные для этого аккаунта
|
|
51
|
+
* (UX «другой способ»). В запрос передаётся {@link RunOtpLoginOptions.supportedMethods}.
|
|
52
|
+
*/
|
|
53
|
+
availableMethods?: VerificationMethod[] | null;
|
|
54
|
+
/** TTL auth-сессии `sendCode` (сек); для Mobile ID — ориентир max wait. */
|
|
55
|
+
sessionLifeTimeInSeconds?: number;
|
|
56
|
+
/** Когда снова можно дернуть `sendCode` (rate-limit hint из ответа). */
|
|
57
|
+
nextAvailableUsageTime?: Date | null;
|
|
32
58
|
};
|
|
33
59
|
/**
|
|
34
|
-
*
|
|
60
|
+
* Аргумент **`getCode`**: только когда нужны цифры (stdin / UI / IMAP снаружи).
|
|
61
|
+
*
|
|
62
|
+
* UI-экран уже выбран в **`onSendCode`** (`usedMethod`). Здесь — собрать код
|
|
63
|
+
* (длина из `codeLength`). На happy-path Mobile ID (push) **`getCode` не вызывается**;
|
|
64
|
+
* SMS-fallback после poll — тот же колбэк, без отдельного `reason`.
|
|
65
|
+
*
|
|
66
|
+
* `accountName` / `accountType` не дублируются — они в options и в `onSendCode`.
|
|
35
67
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
68
|
+
type OtpGetCodeContext = {
|
|
69
|
+
sessionId: string;
|
|
70
|
+
/** Эффективная длина для валидации (Mobile ID SMS fallback: при `0` с сервера → 4). */
|
|
71
|
+
codeLength?: number;
|
|
72
|
+
/** Канал из `sendCode` (опциональная подсказка; экран уже из `onSendCode`). */
|
|
73
|
+
usedMethod?: VerificationMethod;
|
|
74
|
+
};
|
|
75
|
+
/** Превышен лимит `sendCode` (`CODE_REQUEST_LIMIT_EXCEEDED`). */
|
|
76
|
+
declare class OtpCodeRequestLimitExceededError extends Error {
|
|
77
|
+
readonly name = "OtpCodeRequestLimitExceededError";
|
|
78
|
+
readonly minutes?: string;
|
|
79
|
+
readonly seconds?: string;
|
|
80
|
+
readonly traceUrl?: string;
|
|
81
|
+
readonly apiMessage?: string;
|
|
82
|
+
readonly body: unknown;
|
|
83
|
+
constructor(body: unknown);
|
|
42
84
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
readonly
|
|
47
|
-
readonly
|
|
48
|
-
constructor(
|
|
85
|
+
/** Нет Basic на `/auth/token` (`AUTH_HEADER_MISSING`). */
|
|
86
|
+
declare class OtpAuthHeaderMissingError extends Error {
|
|
87
|
+
readonly name = "OtpAuthHeaderMissingError";
|
|
88
|
+
readonly body: unknown;
|
|
89
|
+
readonly systemMessage?: string;
|
|
90
|
+
constructor(body: unknown);
|
|
49
91
|
}
|
|
50
92
|
|
|
51
93
|
/**
|
|
@@ -144,88 +186,10 @@ declare class TokenSession {
|
|
|
144
186
|
private persist;
|
|
145
187
|
}
|
|
146
188
|
|
|
147
|
-
/**
|
|
148
|
-
* Внутренний оркестратор OTP-логина (не публичный export целиком; типы UI — да).
|
|
149
|
-
*
|
|
150
|
-
* **`sendCode`** → ветка по **`usedMethod`** (Mobile ID poll | classic код) →
|
|
151
|
-
* **`TokenResp`**. UI снаружи: **`onSendCode`** + **`getCode`**.
|
|
152
|
-
* IMAP / stdin / env — в app / `examples/node/_shared`, не здесь.
|
|
153
|
-
*
|
|
154
|
-
* Вызывается из `TorrowAuth.loginWithOtp`; Mobile ID — `mobile-id.ts`.
|
|
155
|
-
*/
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Аргумент **`onSendCode`**: снимок **после** успешного `sendCode` (в основном поля **`SendCodeResp`**).
|
|
159
|
-
* Не путать с телом запроса: в `sendCode` уходят `supportedMethods` / `preferredMethod`
|
|
160
|
-
* (см. {@link RunOtpLoginOptions}); здесь — что сервер **выбрал** и что ещё доступно.
|
|
161
|
-
*
|
|
162
|
-
* Типичный UI: по `usedMethod` показать «ждём SMS» / «код на email» / «подтвердите push»;
|
|
163
|
-
* по `codeLength` — маску поля ввода.
|
|
164
|
-
*/
|
|
165
|
-
type OtpSendCodeResult = {
|
|
166
|
-
/** Аккаунт из `SendCodeReq` (телефон / email). */
|
|
167
|
-
accountName: string;
|
|
168
|
-
/** `Phone` \| `Email` из запроса (не путать с `usedMethod`). */
|
|
169
|
-
accountType: SendCodeAccountType;
|
|
170
|
-
/** Id auth-сессии для последующего `requestToken` / Mobile ID poll. */
|
|
171
|
-
sessionId: string;
|
|
172
|
-
/**
|
|
173
|
-
* Фактический канал из **`SendCodeResp.usedMethod`** — ради этого и нужен `onSendCode`.
|
|
174
|
-
* Sms / Email / FlashCall / TelegramGateway / MtsMobileId / …
|
|
175
|
-
*/
|
|
176
|
-
usedMethod?: VerificationMethod;
|
|
177
|
-
/** Длина кода для input/маски; у Mobile ID часто `0` (keypad не нужен). */
|
|
178
|
-
codeLength?: number;
|
|
179
|
-
/** Интервал poll при Mobile ID (`SendCodeResp.pollIntervalSeconds`). */
|
|
180
|
-
pollIntervalSeconds?: number;
|
|
181
|
-
/**
|
|
182
|
-
* Из **`SendCodeResp`** (не тело `sendCode`): методы, доступные для этого аккаунта
|
|
183
|
-
* (UX «другой способ»). В запрос передаётся {@link RunOtpLoginOptions.supportedMethods}.
|
|
184
|
-
*/
|
|
185
|
-
availableMethods?: VerificationMethod[] | null;
|
|
186
|
-
/** TTL auth-сессии `sendCode` (сек); для Mobile ID — ориентир max wait. */
|
|
187
|
-
sessionLifeTimeInSeconds?: number;
|
|
188
|
-
/** Когда снова можно дернуть `sendCode` (rate-limit hint из ответа). */
|
|
189
|
-
nextAvailableUsageTime?: Date | null;
|
|
190
|
-
};
|
|
191
|
-
/**
|
|
192
|
-
* Аргумент **`getCode`**: только когда нужны цифры (stdin / UI / IMAP снаружи).
|
|
193
|
-
*
|
|
194
|
-
* UI-экран уже выбран в **`onSendCode`** (`usedMethod`). Здесь — собрать код
|
|
195
|
-
* (длина из `codeLength`). На happy-path Mobile ID (push) **`getCode` не вызывается**;
|
|
196
|
-
* SMS-fallback после poll — тот же колбэк, без отдельного `reason`.
|
|
197
|
-
*
|
|
198
|
-
* `accountName` / `accountType` не дублируются — они в options и в `onSendCode`.
|
|
199
|
-
*/
|
|
200
|
-
type OtpGetCodeContext = {
|
|
201
|
-
sessionId: string;
|
|
202
|
-
/** Эффективная длина для валидации (Mobile ID SMS fallback: при `0` с сервера → 4). */
|
|
203
|
-
codeLength?: number;
|
|
204
|
-
/** Канал из `sendCode` (опциональная подсказка; экран уже из `onSendCode`). */
|
|
205
|
-
usedMethod?: VerificationMethod;
|
|
206
|
-
};
|
|
207
|
-
/** Превышен лимит `sendCode` (`CODE_REQUEST_LIMIT_EXCEEDED`). */
|
|
208
|
-
declare class OtpCodeRequestLimitExceededError extends Error {
|
|
209
|
-
readonly name = "OtpCodeRequestLimitExceededError";
|
|
210
|
-
readonly minutes?: string;
|
|
211
|
-
readonly seconds?: string;
|
|
212
|
-
readonly traceUrl?: string;
|
|
213
|
-
readonly apiMessage?: string;
|
|
214
|
-
readonly body: unknown;
|
|
215
|
-
constructor(body: unknown);
|
|
216
|
-
}
|
|
217
|
-
/** Нет Basic на `/auth/token` (`AUTH_HEADER_MISSING`). */
|
|
218
|
-
declare class OtpAuthHeaderMissingError extends Error {
|
|
219
|
-
readonly name = "OtpAuthHeaderMissingError";
|
|
220
|
-
readonly body: unknown;
|
|
221
|
-
readonly systemMessage?: string;
|
|
222
|
-
constructor(body: unknown);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
189
|
/**
|
|
226
190
|
* Публичный фасад аутентификации **`TorrowAuth`**.
|
|
227
191
|
*
|
|
228
|
-
* Точка входа приложения: `connect` / `loginWithOtp` / `loginWithLink` /
|
|
192
|
+
* Точка входа приложения: `createOtpFlow` / `connect` / `loginWithOtp` / `loginWithLink` /
|
|
229
193
|
* `fromTokens` / `anonymous`. Возвращает {@link TorrowAuthResult}
|
|
230
194
|
* (`{ client, session }`), кроме `anonymous` (только `TorrowClient`).
|
|
231
195
|
*
|
|
@@ -334,12 +298,15 @@ type TorrowAuthFromTokensOptions = TorrowAuthSessionBase & {
|
|
|
334
298
|
* Фасад аутентификации для приложений и виджетов.
|
|
335
299
|
*
|
|
336
300
|
* - {@link TorrowAuth.connect} — restore / refresh
|
|
301
|
+
* - {@link TorrowAuth.createOtpFlow} — раздельные OTP-шаги без сессии
|
|
337
302
|
* - {@link TorrowAuth.loginWithOtp} — OTP (Phone/Email, Mobile ID / classic)
|
|
338
303
|
* - {@link TorrowAuth.loginWithLink} — Telegram deep-link
|
|
339
304
|
* - {@link TorrowAuth.fromTokens} — сид токенов от хоста
|
|
340
305
|
* - {@link TorrowAuth.anonymous} — клиент без Bearer
|
|
341
306
|
*/
|
|
342
307
|
declare const TorrowAuth: {
|
|
308
|
+
/** Пошаговый OTP без storage, polling и refresh. */
|
|
309
|
+
readonly createOtpFlow: typeof createOtpFlow;
|
|
343
310
|
readonly connect: (options: TorrowAuthConnectOptions) => Promise<TorrowAuthResult>;
|
|
344
311
|
readonly loginWithOtp: (options: TorrowAuthLoginWithOtpOptions) => Promise<TorrowAuthResult>;
|
|
345
312
|
readonly loginWithLink: (options: TorrowAuthLoginWithLinkOptions) => Promise<TorrowAuthResult>;
|
|
@@ -347,4 +314,230 @@ declare const TorrowAuth: {
|
|
|
347
314
|
readonly anonymous: (config?: Pick<TokenSessionOptions, "baseUrl" | "fetch" | "defaultHeaders" | "userAgent">) => TorrowClient;
|
|
348
315
|
};
|
|
349
316
|
|
|
350
|
-
|
|
317
|
+
/**
|
|
318
|
+
* Опции пошагового входа без TokenStorage и пользовательской сессии.
|
|
319
|
+
*
|
|
320
|
+
* HTTP-настройки из {@link TokenSessionOptions} применяются к обоим шагам:
|
|
321
|
+
* - `baseUrl` — адрес шлюза без `/api/v1`; по умолчанию `https://torrow.net`.
|
|
322
|
+
* - `fetch` — реализация HTTP-транспорта; по умолчанию глобальный `fetch`.
|
|
323
|
+
* - `defaultHeaders` — дополнительные заголовки каждого запроса.
|
|
324
|
+
* - `userAgent` — значение заголовка `User-Agent` для диагностики сервера.
|
|
325
|
+
*
|
|
326
|
+
* Между `start` и `complete`, в том числе на разных экземплярах flow,
|
|
327
|
+
* приложение сохраняет одинаковые deviceInfo, scopes и splitTokens.
|
|
328
|
+
*/
|
|
329
|
+
type TorrowAuthOtpFlowOptions = Pick<TokenSessionOptions, "baseUrl" | "fetch" | "defaultHeaders" | "userAgent"> & {
|
|
330
|
+
/**
|
|
331
|
+
* Учётные данные приложения для Basic на `/api/v1/auth/*`.
|
|
332
|
+
* Если не заданы — используется {@link DEFAULT_APP_CREDENTIALS};
|
|
333
|
+
* явная пара полностью заменяет default и требует непустых clientId/clientSecret.
|
|
334
|
+
*/
|
|
335
|
+
appCredentials?: AppCredentials;
|
|
336
|
+
/**
|
|
337
|
+
* Обязательное описание устройства для `TokenReq.deviceInfo` при `complete`.
|
|
338
|
+
* Требуются непустой deviceId и поддерживаемый deviceType; flow не генерирует deviceId.
|
|
339
|
+
*/
|
|
340
|
+
deviceInfo: DeviceInfo;
|
|
341
|
+
/**
|
|
342
|
+
* Запрашиваемые области доступа → `TokenReq.scopes` при `complete`.
|
|
343
|
+
* Если поле отсутствует или массив пуст — `user_api` и `offline_access`.
|
|
344
|
+
*/
|
|
345
|
+
scopes?: AuthScope[];
|
|
346
|
+
/**
|
|
347
|
+
* Разделение access/refresh сервером → `TokenReq.splitTokens` при `complete`.
|
|
348
|
+
* false: полные токены в JSON; используйте для Node, MCP и интеграционных ключей.
|
|
349
|
+
* true: части токенов в JSON, остальные части в HttpOnly cookies;
|
|
350
|
+
* только для браузерного приложения с настроенной передачей cookies.
|
|
351
|
+
* SDK не управляет cookies и не восстанавливает полные токены из частей.
|
|
352
|
+
* Явные true/false передаются без изменения; отсутствующее поле не отправляется.
|
|
353
|
+
*/
|
|
354
|
+
splitTokens?: boolean;
|
|
355
|
+
};
|
|
356
|
+
/**
|
|
357
|
+
* Вход отправки кода; поля наследуются из {@link TorrowAuthLoginWithOtpOptions}.
|
|
358
|
+
* account задаёт Phone/Email и получателя; language — язык (default ru),
|
|
359
|
+
* regionStorageId — регион (default 70), captcha/captchaType — ответ и провайдер captcha,
|
|
360
|
+
* inviteSessionId — приглашение. preferredMethod — пожелание серверу,
|
|
361
|
+
* supportedMethods — способы, которые приложение умеет обработать.
|
|
362
|
+
* UI и получение captcha остаются в приложении.
|
|
363
|
+
*/
|
|
364
|
+
type OtpStartInput = Pick<TorrowAuthLoginWithOtpOptions, "account" | "language" | "regionStorageId" | "captcha" | "captchaType" | "inviteSessionId" | "preferredMethod" | "supportedMethods">;
|
|
365
|
+
/** Опции одного HTTP-шага. */
|
|
366
|
+
type OtpRequestOptions = {
|
|
367
|
+
/** Отмена текущего HTTP-шага; не отменяет независимый cleanup приложения. */
|
|
368
|
+
signal?: AbortSignal;
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Действие приложения для продолжения входа, отдельно от серверного TokenStatus.
|
|
372
|
+
* AwaitingCode — первоначальный ввод classic OTP либо новая фаза ввода SMS-кода
|
|
373
|
+
* после fallback из Pending в той же Mobile ID auth-сессии.
|
|
374
|
+
* Pending — ожидание Mobile ID, complete вызывается без кода.
|
|
375
|
+
*/
|
|
376
|
+
declare enum OtpPhaseValues {
|
|
377
|
+
/** Требуется первоначальный OTP или SMS-код после Mobile ID fallback. */
|
|
378
|
+
AwaitingCode = "AwaitingCode",
|
|
379
|
+
/** Ожидание Mobile ID; следующий запрос выполняется без кода. */
|
|
380
|
+
Pending = "Pending"
|
|
381
|
+
}
|
|
382
|
+
/** JSON-значение фазы; принимает строковый литерал или член {@link OtpPhaseValues}. */
|
|
383
|
+
type OtpPhase = `${OtpPhaseValues}`;
|
|
384
|
+
/** Переносимое продолжение; приложение обеспечивает его целостность. */
|
|
385
|
+
type OtpChallenge = {
|
|
386
|
+
/** Идентификатор auth-сессии из SendCodeResp; сохраняется между шагами. */
|
|
387
|
+
sessionId: string;
|
|
388
|
+
/** Тип получателя первоначального кода: Phone или Email. */
|
|
389
|
+
accountType: SendCodeAccountType;
|
|
390
|
+
/** Фактический способ подтверждения; при SMS fallback меняется на Sms. */
|
|
391
|
+
usedMethod: VerificationMethod;
|
|
392
|
+
/** Следующее действие приложения: ввод кода или ожидание без кода. */
|
|
393
|
+
phase: OtpPhase;
|
|
394
|
+
/** Локальный deadline в Unix milliseconds, без продления; не гарантирует серверный срок. TTL из SendCodeResp либо 180 секунд от начала start. */
|
|
395
|
+
expiresAtMs: number;
|
|
396
|
+
/** Подсказка длины кода из SendCodeResp; default 6 для classic Email, 4 для Phone/Mobile ID. В переносимом challenge может отсутствовать. */
|
|
397
|
+
codeLength?: number;
|
|
398
|
+
/** Интервал ожидания в секундах для Pending; default 3. SDK сам не выполняет polling. */
|
|
399
|
+
pollIntervalSeconds?: number;
|
|
400
|
+
/** Оставшиеся попытки из SendCodeResp.attemptCountToUseSession; SDK не уменьшает их. Отсутствие означает, что корректная подсказка не получена. */
|
|
401
|
+
attemptsRemaining?: number;
|
|
402
|
+
/** Время следующей отправки из SendCodeResp.nextAvailableUsageTime в Unix milliseconds; отсутствует без корректной подсказки. */
|
|
403
|
+
nextSendAtMs?: number;
|
|
404
|
+
/** Доступные способы из SendCodeResp; отсутствуют, если список отсутствовал или содержал неизвестные значения. */
|
|
405
|
+
availableMethods?: VerificationMethod[];
|
|
406
|
+
};
|
|
407
|
+
/** Параметры одной попытки обмена или ожидания. */
|
|
408
|
+
type OtpCompleteInput = {
|
|
409
|
+
/** Переносимое продолжение; проверяется перед запросом, целостность обеспечивает приложение. */
|
|
410
|
+
challenge: OtpChallenge;
|
|
411
|
+
/** Непустой OTP в AwaitingCode; в Pending поле должно отсутствовать. */
|
|
412
|
+
code?: string;
|
|
413
|
+
};
|
|
414
|
+
/** Данные для явного cleanup вызывающим приложением. */
|
|
415
|
+
type OtpCleanupData = {
|
|
416
|
+
/** Refresh-секрет для явного отзыва приложением; не предназначен для диагностики. */
|
|
417
|
+
refreshToken: string;
|
|
418
|
+
};
|
|
419
|
+
/** Причины локального отказа; HTTP и отмена пробрасываются без преобразования. */
|
|
420
|
+
declare enum OtpFlowErrorCodeValues {
|
|
421
|
+
/** Некорректные опции, вход start или код для текущей фазы. */
|
|
422
|
+
InvalidArguments = "InvalidArguments",
|
|
423
|
+
/** Повреждённое, противоречивое или просроченное продолжение. */
|
|
424
|
+
InvalidChallenge = "InvalidChallenge",
|
|
425
|
+
/** Ответ не соответствует строгому контракту шага. */
|
|
426
|
+
InvalidResponse = "InvalidResponse",
|
|
427
|
+
/** Фактический способ подтверждения отсутствует или не поддерживается для аккаунта. */
|
|
428
|
+
UnsupportedMethod = "UnsupportedMethod"
|
|
429
|
+
}
|
|
430
|
+
/** Строковое представление безопасной причины отказа. */
|
|
431
|
+
type OtpFlowErrorCode = `${OtpFlowErrorCodeValues}`;
|
|
432
|
+
/** Отказ с безопасной диагностикой и отдельно доступным секретом для cleanup. */
|
|
433
|
+
declare class OtpFlowError extends Error {
|
|
434
|
+
#private;
|
|
435
|
+
/** Безопасная причина локального отказа, без исходного ответа и секретов. */
|
|
436
|
+
readonly code: OtpFlowErrorCode;
|
|
437
|
+
readonly name = "OtpFlowError";
|
|
438
|
+
constructor(
|
|
439
|
+
/** Безопасная причина локального отказа, без исходного ответа и секретов. */
|
|
440
|
+
code: OtpFlowErrorCode, cleanup?: OtpCleanupData);
|
|
441
|
+
/** Секрет доступен явно; не является перечисляемым полем исключения. */
|
|
442
|
+
get cleanup(): OtpCleanupData | undefined;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Результат одного шага: серверный статус отдельно от фазы challenge.
|
|
446
|
+
* Pending — сервер ещё не завершил аутентификацию, следующий запрос без кода.
|
|
447
|
+
* AwaitingSmsCode — продолжение Mobile ID-сценария через SMS fallback,
|
|
448
|
+
* если аутентификацию через Mobile ID не удалось завершить. Возвращённый challenge
|
|
449
|
+
* меняет фазу Pending на AwaitingCode и метод на Sms; sessionId и deadline сохраняются.
|
|
450
|
+
* TokenReady — токены выданы; Failed — терминальный отказ без продолжения.
|
|
451
|
+
* Обычный Email/SMS OTP начинается с AwaitingCode после start,
|
|
452
|
+
* без отдельного серверного AwaitingEmailCode.
|
|
453
|
+
*/
|
|
454
|
+
type OtpCompleteResult = {
|
|
455
|
+
/** Сервер ещё ожидает подтверждения либо запросил SMS fallback. */
|
|
456
|
+
status: TokenStatusValues.Pending | TokenStatusValues.AwaitingSmsCode;
|
|
457
|
+
/** Продолжение с новым действием приложения и прежним deadline. */
|
|
458
|
+
challenge: OtpChallenge;
|
|
459
|
+
/** Неожиданный refresh для явного отзыва; отсутствует, если сервер его не выдал. */
|
|
460
|
+
cleanup?: OtpCleanupData;
|
|
461
|
+
} | {
|
|
462
|
+
/** Терминальный отказ сервера; продолжение не возвращается. */
|
|
463
|
+
status: TokenStatusValues.Failed;
|
|
464
|
+
/** Неожиданный refresh для явного отзыва; отсутствует без refresh в ответе. */
|
|
465
|
+
cleanup?: OtpCleanupData;
|
|
466
|
+
} | {
|
|
467
|
+
/** Сервер явно подтвердил выдачу токенов. */
|
|
468
|
+
status: TokenStatusValues.TokenReady;
|
|
469
|
+
/**
|
|
470
|
+
* Проверенные поля TokenResp: accessToken обязателен, refreshToken и expiration
|
|
471
|
+
* (TTL в секундах) сохраняются при наличии; splitTokens определяет полноту токенов.
|
|
472
|
+
*/
|
|
473
|
+
tokens: Omit<TokenResp, "status" | "accessToken"> & {
|
|
474
|
+
/** Явный статус готовности из token-ответа. */
|
|
475
|
+
status: TokenStatusValues.TokenReady;
|
|
476
|
+
/** Непустой access token из ответа после нормализации aliases. */
|
|
477
|
+
accessToken: string;
|
|
478
|
+
};
|
|
479
|
+
};
|
|
480
|
+
/** Пошаговый интерфейс: каждый метод выполняет только один upstream-запрос. */
|
|
481
|
+
interface OtpFlow {
|
|
482
|
+
/** Отправляет один код и возвращает переносимое продолжение без токенов. */
|
|
483
|
+
start(input: OtpStartInput, options?: OtpRequestOptions): Promise<OtpChallenge>;
|
|
484
|
+
/** Проверяет продолжение и выполняет одну попытку обмена/ожидания без сохранения. */
|
|
485
|
+
complete(input: OtpCompleteInput, options?: OtpRequestOptions): Promise<OtpCompleteResult>;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Создаёт OTP-flow с фиксированными настройками транспорта и token-обмена.
|
|
489
|
+
* Создание не выполняет HTTP; start и complete выполняют по одному запросу.
|
|
490
|
+
* Challenge и токены остаются у приложения, без storage, polling и refresh внутри flow.
|
|
491
|
+
* @param options Настройки HTTP, credentials приложения, устройства и выдачи токенов.
|
|
492
|
+
* @returns Независимые шаги отправки кода и завершения/ожидания аутентификации.
|
|
493
|
+
* @throws OtpFlowError При некорректных опциях устройства, scopes или splitTokens.
|
|
494
|
+
*/
|
|
495
|
+
declare function createOtpFlow(options: TorrowAuthOtpFlowOptions): OtpFlow;
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* In-memory реализация {@link TokenStorage}.
|
|
499
|
+
*
|
|
500
|
+
* Тесты, эфемерные процессы, `TorrowAuth.fromTokens` без диска.
|
|
501
|
+
* Для Node-примеров обычно {@link FileTokenStorage}; для браузера —
|
|
502
|
+
* {@link LocalStorageTokenStorage}.
|
|
503
|
+
*/
|
|
504
|
+
|
|
505
|
+
/** In-memory хранилище токенов сессии. */
|
|
506
|
+
declare class MemoryTokenStorage implements TokenStorage {
|
|
507
|
+
private snapshot;
|
|
508
|
+
constructor(initial?: AuthTokenSnapshot);
|
|
509
|
+
load(): Promise<AuthTokenSnapshot>;
|
|
510
|
+
save(snapshot: AuthTokenSnapshot): Promise<void>;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Persist snapshot в браузерный **`localStorage`** — реализация {@link TokenStorage}.
|
|
515
|
+
*
|
|
516
|
+
* Для виджетов / SPA (`TorrowAuth.fromTokens` / `connect`). В Node без
|
|
517
|
+
* `globalThis.localStorage` конструктор бросит. Не путать с {@link FileTokenStorage}.
|
|
518
|
+
*/
|
|
519
|
+
|
|
520
|
+
type LocalStorageLike = {
|
|
521
|
+
getItem(key: string): string | null;
|
|
522
|
+
setItem(key: string, value: string): void;
|
|
523
|
+
removeItem?(key: string): void;
|
|
524
|
+
};
|
|
525
|
+
/**
|
|
526
|
+
* Persist snapshot токенов в браузерный `localStorage` (виджет / SPA).
|
|
527
|
+
*/
|
|
528
|
+
declare class LocalStorageTokenStorage implements TokenStorage {
|
|
529
|
+
readonly key: string;
|
|
530
|
+
private readonly store;
|
|
531
|
+
constructor(key: string, store?: LocalStorageLike);
|
|
532
|
+
load(): Promise<AuthTokenSnapshot>;
|
|
533
|
+
save(snapshot: AuthTokenSnapshot): Promise<void>;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** Сессия auth ещё не подтверждена (например Telegram до Start у бота). */
|
|
537
|
+
declare class AuthSessionNotReadyError extends Error {
|
|
538
|
+
readonly name = "AuthSessionNotReadyError";
|
|
539
|
+
readonly cause?: unknown;
|
|
540
|
+
constructor(message?: string, cause?: unknown);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export { AppCredentials, AuthSessionNotReadyError, AuthTokenSnapshot, DEFAULT_APP_CREDENTIALS, LocalStorageTokenStorage, MemoryTokenStorage, OtpAuthHeaderMissingError, type OtpChallenge, type OtpCleanupData, OtpCodeRequestLimitExceededError, type OtpCompleteInput, type OtpCompleteResult, type OtpFlow, OtpFlowError, type OtpFlowErrorCode, OtpFlowErrorCodeValues, type OtpGetCodeContext, type OtpPhase, OtpPhaseValues, type OtpRequestOptions, type OtpSendCodeResult, type OtpStartInput, TokenSession, TokenSessionOptions, TokenSessionStatus, TokenSessionStatusListener, TokenStorage, TorrowAuth, type TorrowAuthAccount, type TorrowAuthConnectOptions, type TorrowAuthFromTokensOptions, type TorrowAuthLoginWithLinkOptions, type TorrowAuthLoginWithOtpOptions, type TorrowAuthOtpFlowOptions, type TorrowAuthResult, type TorrowAuthSessionBase };
|