@torrow/sdk-auth 0.6.0 → 0.8.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 +146 -2
- package/dist/index.d.ts +310 -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,139 @@ flowchart TD
|
|
|
27
28
|
|
|
28
29
|
## Целевой клиентский код
|
|
29
30
|
|
|
31
|
+
### Приоритет способов подтверждения для телефона
|
|
32
|
+
|
|
33
|
+
Порядок `supportedMethods` имеет значение: передавайте поддерживаемые клиентом способы в порядке приоритета. Для телефона рекомендуется следующий порядок (для `loginWithOtp`, `createOtpFlow().start` и low-level `sendCode`):
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { VerificationMethodValues } from "@torrow/sdk-client";
|
|
37
|
+
|
|
38
|
+
supportedMethods: [
|
|
39
|
+
VerificationMethodValues.TelegramGateway,
|
|
40
|
+
VerificationMethodValues.MtsMobileId,
|
|
41
|
+
VerificationMethodValues.Sms,
|
|
42
|
+
],
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`preferredMethod` задаёт отдельное пожелание, а фактический способ сервер возвращает в `usedMethod`. Указывайте только способы, которые приложение умеет обрабатывать. Примеры, специально посвящённые Mobile ID, задают свой список; пример classic OTP без polling исключает `MtsMobileId` и сохраняет порядок `TelegramGateway`, `Sms`.
|
|
46
|
+
|
|
47
|
+
### Пошаговый OTP без пользовательской сессии
|
|
48
|
+
|
|
49
|
+
`TorrowAuth.createOtpFlow` выполняет один `sendCode` на start и один `requestToken` на complete. Он не хранит challenge или токены, не требует TokenStorage/callbacks, не делает polling, refresh, retry или revoke. Для полного входа с callbacks и сохранением используйте `loginWithOtp`.
|
|
50
|
+
|
|
51
|
+
```mermaid
|
|
52
|
+
sequenceDiagram
|
|
53
|
+
actor User as Пользователь
|
|
54
|
+
participant App as Приложение
|
|
55
|
+
participant Flow as OtpFlow (@torrow/sdk-auth)
|
|
56
|
+
participant API as Torrow API
|
|
57
|
+
|
|
58
|
+
App->>Flow: createOtpFlow(options)
|
|
59
|
+
App->>Flow: start({ account, ... }, { signal })
|
|
60
|
+
Flow->>API: POST /auth/sendcode (Basic приложения)
|
|
61
|
+
API-->>Flow: SendCodeResp
|
|
62
|
+
Flow-->>App: OtpChallenge
|
|
63
|
+
Note over App,Flow: Classic — AwaitingCode, Mobile ID — Pending.<br/>Challenge не содержит токенов или OTP.
|
|
64
|
+
App->>App: Защитить и перенести challenge через JSON
|
|
65
|
+
Note over App,Flow: complete можно вызвать на новом экземпляре flow.<br/>deviceInfo, scopes и splitTokens сохраняет приложение.
|
|
66
|
+
|
|
67
|
+
loop Пока нет TokenReady / Failed и challenge не просрочен
|
|
68
|
+
alt challenge.phase = AwaitingCode
|
|
69
|
+
App->>User: Запросить OTP / SMS-код
|
|
70
|
+
User-->>App: code
|
|
71
|
+
App->>Flow: complete({ challenge, code }, { signal })
|
|
72
|
+
else challenge.phase = Pending
|
|
73
|
+
App->>App: При повторном polling дождаться интервала
|
|
74
|
+
App->>Flow: complete({ challenge }, { signal })
|
|
75
|
+
end
|
|
76
|
+
Note right of Flow: Проверить challenge до HTTP.<br/>Pending — wire code пустой.
|
|
77
|
+
Flow->>API: POST /auth/token (Basic приложения)
|
|
78
|
+
API-->>Flow: TokenResp
|
|
79
|
+
alt status = Pending
|
|
80
|
+
Flow-->>App: Pending + challenge (phase = Pending)<br/>+ cleanup, если есть refresh
|
|
81
|
+
else status = AwaitingSmsCode
|
|
82
|
+
Flow-->>App: SMS fallback той же Mobile ID сессии<br/>AwaitingSmsCode + challenge<br/>(новая phase = AwaitingCode, usedMethod = Sms)<br/>+ cleanup, если есть refresh
|
|
83
|
+
else status = TokenReady
|
|
84
|
+
Flow-->>App: TokenReady + tokens
|
|
85
|
+
App->>App: Использовать токены и решить вопрос хранения / cleanup
|
|
86
|
+
else status = Failed
|
|
87
|
+
Flow-->>App: Failed + cleanup, если есть refresh
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
Note over App,Flow: expiresAtMs не продлевается между шагами.<br/>Ошибки передаются приложению, скрытых повторов нет.<br/>Cleanup выполняет приложение с независимым AbortSignal.
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { TorrowAuth, OtpFlowError, type OtpChallenge } from "@torrow/sdk-auth";
|
|
96
|
+
import { TokenStatusValues } from "@torrow/sdk-client";
|
|
97
|
+
|
|
98
|
+
const options = {
|
|
99
|
+
deviceInfo: { deviceId: "my-app-device", deviceType: "Desktop" as const },
|
|
100
|
+
scopes: ["user_api" as const],
|
|
101
|
+
splitTokens: false,
|
|
102
|
+
};
|
|
103
|
+
const challenge = await TorrowAuth.createOtpFlow(options).start({
|
|
104
|
+
account: { accountType: "Email", accountName: "user@example.com" },
|
|
105
|
+
});
|
|
106
|
+
// Между запросами приложение защищает целостность этого JSON.
|
|
107
|
+
const continuation: OtpChallenge = JSON.parse(JSON.stringify(challenge));
|
|
108
|
+
try {
|
|
109
|
+
const result = await TorrowAuth.createOtpFlow(options).complete(
|
|
110
|
+
{ challenge: continuation, code: "123456" },
|
|
111
|
+
{ signal: AbortSignal.timeout(15000) },
|
|
112
|
+
);
|
|
113
|
+
if (result.status === TokenStatusValues.TokenReady) {
|
|
114
|
+
// Использовать result.tokens.accessToken в рамках текущего запроса.
|
|
115
|
+
// Сохранение/cleanup result.tokens.refreshToken определяет приложение.
|
|
116
|
+
} else {
|
|
117
|
+
// result.cleanup?.refreshToken доступен для отдельного cleanup.
|
|
118
|
+
if ("challenge" in result) {
|
|
119
|
+
// Защитить result.challenge для следующего шага.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (error instanceof OtpFlowError) {
|
|
124
|
+
// error.code — безопасная причина; error.cleanup?.refreshToken — секрет для cleanup.
|
|
125
|
+
// Cleanup выполняет приложение с независимым AbortSignal.
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| Значение | Действие приложения |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `challenge.phase: AwaitingCode` | Для следующего шага требуется непустой код. Это первоначальный ввод при обычном OTP либо ввод SMS-кода после fallback в Mobile ID-сценарии. |
|
|
134
|
+
| `challenge.phase: Pending` | Продолжается ожидание результата Mobile ID. Следующий complete вызывается без кода; wire code будет пустым. |
|
|
135
|
+
| `result.status: Pending` | Сервер ещё не завершил аутентификацию. SDK возвращает challenge с фазой Pending; приложение планирует следующий запрос без кода. Возможен возврат к Pending после отправки SMS-кода. |
|
|
136
|
+
| `result.status: AwaitingSmsCode` | Сервер перевёл Mobile ID-сценарий на SMS fallback, поскольку аутентификацию через Mobile ID не удалось завершить. SDK возвращает challenge с новой фазой AwaitingCode и методом Sms; приложение запрашивает SMS-код и передаёт его в следующий complete. |
|
|
137
|
+
| `result.status: Failed` | Текущая попытка аутентификации завершилась отказом. Challenge для продолжения не возвращается; новый вход требует отдельного start. |
|
|
138
|
+
| `result.status: TokenReady` | Аутентификация завершена, токены получены и проверены SDK. Продолжение не требуется; SDK не сохраняет токены. |
|
|
139
|
+
|
|
140
|
+
`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`.
|
|
141
|
+
|
|
142
|
+
Опции 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 поддерживается; неизвестный метод отклоняется.
|
|
143
|
+
|
|
144
|
+
**Когда использовать `splitTokens`.** Это параметр `POST /api/v1/auth/token`, который обрабатывает сервер: SDK передаёт флаг и возвращает полученные поля токенов без самостоятельного разделения или восстановления.
|
|
145
|
+
|
|
146
|
+
| Значение | Поведение сервера и сценарий |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `false` | Возвращает полные access/refresh в JSON и удаляет cookies ранее разделённых токенов. Используйте для Node, MCP и приложений, которым нужны полные токены для API-вызовов, выпуска интеграционного ключа и cleanup. |
|
|
149
|
+
| `true` | При наличии access token разделяет access/refresh: одну часть каждого токена помещает в HttpOnly cookies, другую возвращает в JSON. Используйте только для браузерного приложения, специально настроенного на этот режим авторизации. При Mobile ID без готовых токенов разделение пропускается. |
|
|
150
|
+
| Не задано | Flow не отправляет поле. В текущей реализации шлюза отсутствующий boolean имеет значение `false`; для явно выбранного режима полных токенов задавайте `false`. |
|
|
151
|
+
|
|
152
|
+
В режиме `true` поля `result.tokens` содержат части токенов, а не самостоятельные полные Bearer/refresh-токены. Последующие запросы должны передавать необходимые части и cookies для восстановления токенов шлюзом. Приложение обеспечивает работу с cookies, включая `credentials` у `fetch` и разрешения CORS при запросах между origin. SDK не добавляет cookie jar или управление cookies; стандартный Node `fetch` не сохраняет и не передаёт их автоматически между запросами. Поэтому Node-примеры задают `splitTokens: false`. Тесты SDK проверяют передачу обоих значений в HTTP-тело, а не серверное разделение или браузерную работу cookies.
|
|
153
|
+
|
|
154
|
+
Challenge: `sessionId`, `accountType`, `usedMethod`, `phase`, `expiresAtMs` и проверенные подсказки `codeLength`, `pollIntervalSeconds`, `attemptsRemaining`, `nextSendAtMs`, `availableMethods`. Даты — Unix milliseconds, пригодные для JSON. Токенов/OTP в challenge нет. Проверка структуры SDK не заменяет проверку целостности приложением. Между шагами приложение сохраняет тот же deviceInfo, scopes и splitTokens; flow не связывает их криптографически с challenge.
|
|
155
|
+
|
|
156
|
+
Срок отсчитывается от начала start: серверный sessionLifeTimeInSeconds либо локальные 180 секунд при отсутствии/null. Некорректный TTL отклоняется. Локальный срок не гарантирует действие сессии на сервере и не продлевается. Ограничение 600 секунд относится к MCP. Polling hint default — 3 секунды. Некорректные необязательные подсказки пропускаются; длина кода — корректная серверная либо Email 6, Phone/SMS fallback 4. Число попыток не уменьшается самостоятельно.
|
|
157
|
+
|
|
158
|
+
Перед проверкой 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-адаптацию общей реализации.
|
|
159
|
+
|
|
160
|
+
`OtpFlowError.code`: InvalidArguments, InvalidChallenge, InvalidResponse, UnsupportedMethod. Getter `error.cleanup` передаёт обнаруженный refresh даже при повреждённом ответе; он не включён в обычную сериализацию/диагностику ошибки. Валидные незавершённые/Failed ответы передают refresh через result.cleanup. HTTP-ошибки остаются TorrowHttpError, сеть и отмена пробрасываются. Потеря ответа не разрешает слепой повтор. Signal передаётся до fetch на обоих шагах; некорректный/просроченный challenge отклоняется до upstream.
|
|
161
|
+
|
|
162
|
+
Примеры: auth-otp-flow.ts (JSON и новый flow), auth-otp-flow-mobile-id.ts (polling приложения и SMS fallback). Выпуск интеграционного ключа, подтверждение identity и cleanup выполняются приложением.
|
|
163
|
+
|
|
30
164
|
Общая база `{ tokenStorage, deviceInfo }` (+ опционально `appCredentials`) у режимов с сессией.
|
|
31
165
|
Без `appCredentials` — **`DEFAULT_APP_CREDENTIALS`** (`sdk.torrow.net`). Своя пара перекрывает дефолт.
|
|
32
166
|
|
|
@@ -35,7 +169,7 @@ flowchart TD
|
|
|
35
169
|
```ts
|
|
36
170
|
import { TorrowAuth } from "@torrow/sdk-auth";
|
|
37
171
|
import { FileTokenStorage } from "@torrow/sdk-auth/node";
|
|
38
|
-
import { DeviceTypeValues } from "@torrow/sdk-client";
|
|
172
|
+
import { DeviceTypeValues, VerificationMethodValues } from "@torrow/sdk-client";
|
|
39
173
|
|
|
40
174
|
const base = {
|
|
41
175
|
// appCredentials опционален — иначе DEFAULT_APP_CREDENTIALS
|
|
@@ -50,6 +184,11 @@ if (session.status !== "authenticated") {
|
|
|
50
184
|
({ client, session } = await TorrowAuth.loginWithOtp({
|
|
51
185
|
...base,
|
|
52
186
|
account: { accountType: "Phone", accountName: "+79001234567" },
|
|
187
|
+
supportedMethods: [
|
|
188
|
+
VerificationMethodValues.TelegramGateway,
|
|
189
|
+
VerificationMethodValues.MtsMobileId,
|
|
190
|
+
VerificationMethodValues.Sms,
|
|
191
|
+
],
|
|
53
192
|
onSendCode: (d) => showHint(d.usedMethod, d.codeLength),
|
|
54
193
|
getCode: (ctx) => promptUser(ctx.codeLength),
|
|
55
194
|
}));
|
|
@@ -68,12 +207,17 @@ appCredentials: { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET },
|
|
|
68
207
|
|
|
69
208
|
```ts
|
|
70
209
|
import { OtpCodeRequestLimitExceededError } from "@torrow/sdk-auth";
|
|
210
|
+
import { VerificationMethodValues } from "@torrow/sdk-client";
|
|
71
211
|
|
|
72
212
|
try {
|
|
73
213
|
await TorrowAuth.loginWithOtp({
|
|
74
214
|
...base,
|
|
75
215
|
account: { accountType: "Phone", accountName: phone },
|
|
76
|
-
supportedMethods: [
|
|
216
|
+
supportedMethods: [
|
|
217
|
+
VerificationMethodValues.TelegramGateway,
|
|
218
|
+
VerificationMethodValues.MtsMobileId,
|
|
219
|
+
VerificationMethodValues.Sms,
|
|
220
|
+
],
|
|
77
221
|
preferredMethod: userPickedMethod,
|
|
78
222
|
onSendCode: (d) => {
|
|
79
223
|
// MtsMobileId → UI «подтвердите push»; getCode не вызовется на happy-path
|