@torrow/sdk-auth 0.8.0 → 0.9.1
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 +27 -9
- package/dist/index.d.ts +27 -56
- package/dist/index.js +819 -430
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/{types-C4e90uOw.d.ts → types-M7DBG6bO.d.ts} +6 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -92,8 +92,8 @@ sequenceDiagram
|
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
```ts
|
|
95
|
-
import { TorrowAuth,
|
|
96
|
-
import { TokenStatusValues } from "@torrow/sdk-client";
|
|
95
|
+
import { TorrowAuth, type OtpChallenge } from "@torrow/sdk-auth";
|
|
96
|
+
import { TokenStatusValues, TorrowSdkError } from "@torrow/sdk-client";
|
|
97
97
|
|
|
98
98
|
const options = {
|
|
99
99
|
deviceInfo: { deviceId: "my-app-device", deviceType: "Desktop" as const },
|
|
@@ -120,8 +120,9 @@ try {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
} catch (error) {
|
|
123
|
-
if (error instanceof
|
|
124
|
-
// error.
|
|
123
|
+
if (error instanceof TorrowSdkError) {
|
|
124
|
+
// error.detail — безопасная предметная причина;
|
|
125
|
+
// error.getSensitiveDetails().cleanup?.refreshToken — секрет для cleanup.
|
|
125
126
|
// Cleanup выполняет приложение с независимым AbortSignal.
|
|
126
127
|
}
|
|
127
128
|
throw error;
|
|
@@ -157,7 +158,7 @@ Challenge: `sessionId`, `accountType`, `usedMethod`, `phase`, `expiresAtMs` и
|
|
|
157
158
|
|
|
158
159
|
Перед проверкой 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
|
|
|
160
|
-
`
|
|
161
|
+
`TorrowSdkError.category` достаточна для общей обработки. `detail` сохраняет домен `TorrowSdkErrorDomain.Otp` и необязательную дополнительную причину `OtpErrorReason.InvalidChallenge`, `UnsupportedMethod`, `AuthHeaderMissing` или `CodeRequestLimitExceeded`; generic InvalidInput/InvalidResponse не дублируются причинами. Метод `error.getSensitiveDetails().cleanup` передаёт обнаруженный refresh даже при повреждённом ответе; он не включён в обычную сериализацию/диагностику ошибки. Валидные незавершённые/Failed ответы передают refresh через result.cleanup. HTTP, сеть, отмена и предметные отказы используют общий TorrowSdkError. OTP start/complete не создают effects; status=Failed остаётся результатом. Потеря ответа не разрешает слепой повтор. Signal передаётся до fetch на обоих шагах; некорректный/просроченный challenge отклоняется до upstream.
|
|
161
162
|
|
|
162
163
|
Примеры: auth-otp-flow.ts (JSON и новый flow), auth-otp-flow-mobile-id.ts (polling приложения и SMS fallback). Выпуск интеграционного ключа, подтверждение identity и cleanup выполняются приложением.
|
|
163
164
|
|
|
@@ -206,8 +207,8 @@ appCredentials: { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET },
|
|
|
206
207
|
### 2. Mobile ID / смена канала / rate-limit
|
|
207
208
|
|
|
208
209
|
```ts
|
|
209
|
-
import {
|
|
210
|
-
import { VerificationMethodValues } from "@torrow/sdk-client";
|
|
210
|
+
import { TorrowAuth } from "@torrow/sdk-auth";
|
|
211
|
+
import { TorrowSdkError, TorrowSdkErrorDomain, OtpErrorReason, VerificationMethodValues } from "@torrow/sdk-client";
|
|
211
212
|
|
|
212
213
|
try {
|
|
213
214
|
await TorrowAuth.loginWithOtp({
|
|
@@ -226,8 +227,8 @@ try {
|
|
|
226
227
|
getCode: (ctx) => waitCodeInput(ctx),
|
|
227
228
|
});
|
|
228
229
|
} catch (e) {
|
|
229
|
-
if (e instanceof
|
|
230
|
-
showRetryTimer(e.
|
|
230
|
+
if (e instanceof TorrowSdkError && e.detail?.domain === TorrowSdkErrorDomain.Otp && e.detail.reason === OtpErrorReason.CodeRequestLimitExceeded) {
|
|
231
|
+
showRetryTimer(e.detail.retryAfterSeconds);
|
|
231
232
|
} else {
|
|
232
233
|
throw e;
|
|
233
234
|
}
|
|
@@ -462,3 +463,20 @@ Host: non-JWT-shaped opaque **или** валидный JWT с `exp`.
|
|
|
462
463
|
(см. **`examples/node/auth/auth-send-code.ts`**).
|
|
463
464
|
|
|
464
465
|
**401-retry** вызывает только **refresh**, не OTP.
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
## Ошибки пользовательской сессии
|
|
469
|
+
|
|
470
|
+
`TokenSession.open/getAccessToken/ensureFresh/requestTokenWithCode/refresh/applyTokenResp/clearTokens/logout` и `TorrowAuth.connect/fromTokens` сообщают отказ через `TorrowSdkError` из `@torrow/sdk-client`. `category` описывает причину, `detail` содержит домен `AuthSession` и optional reason только для `SessionNotReady` / `CallbackFailed`; generic и storage-причины удалены, этапы storage различает `failureAt`, `operation` и `failureAt` называют метод и локальную стадию без аргументов. `AuthSessionNotReadyError` удалён: ожидающую подтверждения сессию распознавайте через `detail.domain === TorrowSdkErrorDomain.AuthSession` и `detail.reason === AuthSessionErrorReason.SessionNotReady`. Штатный polling deep-link сохранён.
|
|
471
|
+
|
|
472
|
+
`effects` описывает только предметные изменения текущего вызова: Remote (`IssueTokens`, `RefreshTokens`, `RevokeTokens`), SessionMemory (`SetTokens`, `ClearTokens`, `CloseSession`), Storage (`SaveTokens`, `ClearStorage`). `Applied` подтверждает шаг, `Unknown` не разрешает слепой повтор. После успешного refresh и ошибки `storage.save` серверный refresh и новая память имеют `Applied`, сохранение — `Unknown`, причина — `StorageFailure`. Память не откатывается. У `fromTokens` подтверждение сохранённого seed сохраняется при ошибке последующего connect. При logout ошибка revoke подавляется; если позже падает storage, неизвестный результат revoke остаётся в effects, сессия остаётся закрытой и память очищенной.
|
|
473
|
+
|
|
474
|
+
`onRefreshFailure` получает `TorrowSdkError`. По умолчанию HTTP 429/502/503/504 и все не-HTTP причины (включая storage) выбирают `keep`; остальные HTTP отказы — `clear`. `keep` не выполняет дополнительную очистку, но не отменяет уже изменённую память. `ensureFresh/getAccessToken/connect` с политикой keep/clear сохраняют прежнее завершение без throw; явный refresh сообщает отказ. Callback `throw` делает ошибку наблюдаемой и при проактивном refresh. Storage-сбой при очистке сообщает собственную причину с выполненными шагами, а не ошибку сервера.
|
|
475
|
+
|
|
476
|
+
Обычный запрос клиента с 401-refresh не обещает полный аудит авторизации. Отсутствие effects означает отсутствие сведений, а не отсутствие изменений. Последняя сохранённая refresh-причина и параллельные вызовы не переносят историю независимых операций. Прямые `TokenStorage.load/save` сохраняют собственные исключения; штатные fallback хранилищ не меняются. Исходная причина/ответ доступны через `getSensitiveDetails()` и могут содержать секреты; обычные message/JSON/console безопасны.
|
|
477
|
+
|
|
478
|
+
### Диагностика составного логина
|
|
479
|
+
|
|
480
|
+
`loginWithOtp` и `loginWithLink` сообщают `operation`, локальную `failureAt` и неизменяемые `effects` выбранных шагов текущего вызова: инициализация сессии, отправка кода (`SendCode`) либо создание продолжения ссылки (`CreateAuthSession`), выдача токенов, память и сохранение. Подтверждённый шаг сохраняет `Applied`, даже если следующий callback или storage завершается ошибкой. Возможные внутренние действия callback не перечисляются.
|
|
481
|
+
|
|
482
|
+
При ошибке сохранения после token-ответа серверная выдача и память могут быть `Applied`, а storage — `Unknown`. Отсутствие effects у обычной сетевой ошибки или пошагового flow не доказывает отсутствие изменений. Диагностика не разрешает слепой повтор; приложение принимает решение о восстановлении. Исходные body/URL/cause и cleanup доступны явно через `getSensitiveDetails()` и не предназначены для обычного логирования.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as AppCredentials, T as TokenSessionOptions, a as TokenStorage, b as TokenSessionStatus, c as TokenSessionStatusListener, d as AuthTokenSnapshot } from './types-
|
|
2
|
-
export { R as RefreshFailureAction } from './types-
|
|
3
|
-
import { SendCodeAccountType, VerificationMethod, TorrowClient, DeviceInfo, TokenResp, AuthScope, AuthCaptchaType, AccountType, TokenStatusValues } from '@torrow/sdk-client';
|
|
1
|
+
import { A as AppCredentials, T as TokenSessionOptions, a as TokenStorage, b as TokenSessionStatus, c as TokenSessionStatusListener, d as AuthTokenSnapshot } from './types-M7DBG6bO.js';
|
|
2
|
+
export { R as RefreshFailureAction } from './types-M7DBG6bO.js';
|
|
3
|
+
import { SendCodeAccountType, VerificationMethod, TorrowSdkEffect, TorrowClient, DeviceInfo, TokenResp, AuthScope, AuthCaptchaType, AccountType, TokenStatusValues } from '@torrow/sdk-client';
|
|
4
4
|
|
|
5
5
|
/** Defaults и преобразование credentials приложения в HTTP Basic. */
|
|
6
6
|
|
|
@@ -72,23 +72,6 @@ type OtpGetCodeContext = {
|
|
|
72
72
|
/** Канал из `sendCode` (опциональная подсказка; экран уже из `onSendCode`). */
|
|
73
73
|
usedMethod?: VerificationMethod;
|
|
74
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);
|
|
84
|
-
}
|
|
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);
|
|
91
|
-
}
|
|
92
75
|
|
|
93
76
|
/**
|
|
94
77
|
* Ядро пользовательской сессии: persist + проактивный refresh + single-flight + 401-retry.
|
|
@@ -114,7 +97,18 @@ declare class TokenSession {
|
|
|
114
97
|
/** Set in refreshWithFailurePolicy catch; not cleared on success (see public refresh). */
|
|
115
98
|
private lastRefreshError;
|
|
116
99
|
private constructor();
|
|
100
|
+
/** Открыть сессию; storage/refresh ошибки описывают только выполненную инициализацию. */
|
|
117
101
|
static open(opts: TokenSessionOptions): Promise<TokenSession>;
|
|
102
|
+
/**
|
|
103
|
+
* @internal Инициализация с явными сведениями только текущего вызова для составного login.
|
|
104
|
+
* Не наблюдает fetch/storage и не хранит историю независимых операций.
|
|
105
|
+
*/
|
|
106
|
+
static openWithEffects(opts: TokenSessionOptions): Promise<{
|
|
107
|
+
/** Открытая сессия с прежней семантикой open. */
|
|
108
|
+
session: TokenSession;
|
|
109
|
+
/** Неизменяемый снимок реально выполненных шагов инициализации. */
|
|
110
|
+
effects: readonly TorrowSdkEffect[];
|
|
111
|
+
}>;
|
|
118
112
|
/** Persist, переданный в `TokenSession.open` / `TorrowAuth.*`. */
|
|
119
113
|
get tokenStorage(): TokenStorage;
|
|
120
114
|
get client(): TorrowClient;
|
|
@@ -126,6 +120,7 @@ declare class TokenSession {
|
|
|
126
120
|
getTokenSnapshot(): AuthTokenSnapshot;
|
|
127
121
|
/** `deviceInfo` из options open или fallback Desktop + deviceId из tokenSnapshot. */
|
|
128
122
|
getDeviceInfo(): DeviceInfo;
|
|
123
|
+
/** Получить access после проактивного refresh; closed возвращает undefined без записи. */
|
|
129
124
|
getAccessToken(): Promise<string | undefined>;
|
|
130
125
|
/**
|
|
131
126
|
* Сохранить ответ `requestToken` / `refresh` (только TokenReady).
|
|
@@ -135,6 +130,7 @@ declare class TokenSession {
|
|
|
135
130
|
applyTokenResp(tokens: TokenResp, opts?: {
|
|
136
131
|
replace?: boolean;
|
|
137
132
|
}): Promise<void>;
|
|
133
|
+
/** Очистить память и сохранить snapshot; storage-сбой не отменяет очистку памяти. */
|
|
138
134
|
clearTokens(): Promise<void>;
|
|
139
135
|
/**
|
|
140
136
|
* Revoke refresh (если есть) + очистка storage.
|
|
@@ -147,15 +143,19 @@ declare class TokenSession {
|
|
|
147
143
|
*
|
|
148
144
|
* Один вызов `POST /api/v1/auth/token` (`requestToken`) с `{ sessionId, code }`.
|
|
149
145
|
* При успехе (`TokenReady`) — persist; если сессия ещё не готова —
|
|
150
|
-
*
|
|
146
|
+
* `TorrowSdkError` с причиной `SessionNotReady` (поллинг для deep-link — внутри `TorrowAuth.loginWithLink`).
|
|
151
147
|
*
|
|
152
148
|
* HTTP вне mutex; persist сериализован через {@link applyTokenResp} (не оборачивать
|
|
153
149
|
* весь метод в `runExclusive` — вложенный mutex с публичным `applyTokenResp`).
|
|
154
150
|
*/
|
|
155
151
|
requestTokenWithCode(args: {
|
|
152
|
+
/** Идентификатор ранее созданной серверной auth-сессии. */
|
|
156
153
|
sessionId: string;
|
|
154
|
+
/** Одноразовый код доступа для выдачи токенов. */
|
|
157
155
|
code: string;
|
|
156
|
+
/** Права токенов; при отсутствии используются DefaultAuthScopes. */
|
|
158
157
|
scopes?: AuthScope[];
|
|
158
|
+
/** Отмена единственного серверного запроса выдачи. */
|
|
159
159
|
signal?: AbortSignal;
|
|
160
160
|
}): Promise<TokenResp>;
|
|
161
161
|
/**
|
|
@@ -165,6 +165,7 @@ declare class TokenSession {
|
|
|
165
165
|
* не используй return value; смотри `session.status` / `getTokenSnapshot()`.
|
|
166
166
|
*/
|
|
167
167
|
refresh(): Promise<TokenResp>;
|
|
168
|
+
/** Обновить просроченный access; политика keep/clear сохраняет прежнее resolve-поведение. */
|
|
168
169
|
ensureFresh(): Promise<void>;
|
|
169
170
|
private assertNotClosedForWrite;
|
|
170
171
|
/** Caller must hold `runExclusive`. Do not call public `applyTokenResp` from here. */
|
|
@@ -172,7 +173,7 @@ declare class TokenSession {
|
|
|
172
173
|
private clearTokensUnlocked;
|
|
173
174
|
/** Caller must hold `runExclusive`. `refreshWithFailurePolicy` skips when no refreshToken; guard here is intentional defense-in-depth. */
|
|
174
175
|
private refreshUnlocked;
|
|
175
|
-
/** Caller must hold `runExclusive
|
|
176
|
+
/** Caller must hold `runExclusive`; сведения принадлежат только текущей операции. */
|
|
176
177
|
private refreshWithFailurePolicy;
|
|
177
178
|
private runExclusive;
|
|
178
179
|
private ensureFreshImpl;
|
|
@@ -308,9 +309,11 @@ type TorrowAuthFromTokensOptions = TorrowAuthSessionBase & {
|
|
|
308
309
|
declare const TorrowAuth: {
|
|
309
310
|
/** Пошаговый OTP без storage, polling и refresh. */
|
|
310
311
|
readonly createOtpFlow: typeof createOtpFlow;
|
|
312
|
+
/** Загрузить сессию; общий отказ сохраняет текущую диагностику open/refresh. */
|
|
311
313
|
readonly connect: (options: TorrowAuthConnectOptions) => Promise<TorrowAuthResult>;
|
|
312
314
|
readonly loginWithOtp: (options: TorrowAuthLoginWithOtpOptions) => Promise<TorrowAuthResult>;
|
|
313
315
|
readonly loginWithLink: (options: TorrowAuthLoginWithLinkOptions) => Promise<TorrowAuthResult>;
|
|
316
|
+
/** Сохранить host tokens, затем connect; последующий отказ не отменяет Applied сохранение. */
|
|
314
317
|
readonly fromTokens: (options: TorrowAuthFromTokensOptions) => Promise<TorrowAuthResult>;
|
|
315
318
|
readonly anonymous: (config?: Pick<TokenSessionOptions, "baseUrl" | "fetch" | "defaultHeaders" | "userAgent">) => TorrowClient;
|
|
316
319
|
};
|
|
@@ -418,31 +421,6 @@ type OtpCleanupData = {
|
|
|
418
421
|
/** Refresh-секрет для явного отзыва приложением; не предназначен для диагностики. */
|
|
419
422
|
refreshToken: string;
|
|
420
423
|
};
|
|
421
|
-
/** Причины локального отказа; HTTP и отмена пробрасываются без преобразования. */
|
|
422
|
-
declare enum OtpFlowErrorCodeValues {
|
|
423
|
-
/** Некорректные опции, вход start или код для текущей фазы. */
|
|
424
|
-
InvalidArguments = "InvalidArguments",
|
|
425
|
-
/** Повреждённое, противоречивое или просроченное продолжение. */
|
|
426
|
-
InvalidChallenge = "InvalidChallenge",
|
|
427
|
-
/** Ответ не соответствует строгому контракту шага. */
|
|
428
|
-
InvalidResponse = "InvalidResponse",
|
|
429
|
-
/** Фактический способ подтверждения отсутствует или не поддерживается для аккаунта. */
|
|
430
|
-
UnsupportedMethod = "UnsupportedMethod"
|
|
431
|
-
}
|
|
432
|
-
/** Строковое представление безопасной причины отказа. */
|
|
433
|
-
type OtpFlowErrorCode = `${OtpFlowErrorCodeValues}`;
|
|
434
|
-
/** Отказ с безопасной диагностикой и отдельно доступным секретом для cleanup. */
|
|
435
|
-
declare class OtpFlowError extends Error {
|
|
436
|
-
#private;
|
|
437
|
-
/** Безопасная причина локального отказа, без исходного ответа и секретов. */
|
|
438
|
-
readonly code: OtpFlowErrorCode;
|
|
439
|
-
readonly name = "OtpFlowError";
|
|
440
|
-
constructor(
|
|
441
|
-
/** Безопасная причина локального отказа, без исходного ответа и секретов. */
|
|
442
|
-
code: OtpFlowErrorCode, cleanup?: OtpCleanupData);
|
|
443
|
-
/** Секрет доступен явно; не является перечисляемым полем исключения. */
|
|
444
|
-
get cleanup(): OtpCleanupData | undefined;
|
|
445
|
-
}
|
|
446
424
|
/**
|
|
447
425
|
* Результат одного шага: серверный статус отдельно от фазы challenge.
|
|
448
426
|
* Pending — сервер ещё не завершил аутентификацию, следующий запрос без кода.
|
|
@@ -492,7 +470,7 @@ interface OtpFlow {
|
|
|
492
470
|
* Challenge и токены остаются у приложения, без storage, polling и refresh внутри flow.
|
|
493
471
|
* @param options Настройки HTTP, credentials приложения, устройства и выдачи токенов.
|
|
494
472
|
* @returns Независимые шаги отправки кода и завершения/ожидания аутентификации.
|
|
495
|
-
* @throws
|
|
473
|
+
* @throws TorrowSdkError При некорректных опциях устройства, scopes или splitTokens.
|
|
496
474
|
*/
|
|
497
475
|
declare function createOtpFlow(options: TorrowAuthOtpFlowOptions): OtpFlow;
|
|
498
476
|
|
|
@@ -535,11 +513,4 @@ declare class LocalStorageTokenStorage implements TokenStorage {
|
|
|
535
513
|
save(snapshot: AuthTokenSnapshot): Promise<void>;
|
|
536
514
|
}
|
|
537
515
|
|
|
538
|
-
|
|
539
|
-
declare class AuthSessionNotReadyError extends Error {
|
|
540
|
-
readonly name = "AuthSessionNotReadyError";
|
|
541
|
-
readonly cause?: unknown;
|
|
542
|
-
constructor(message?: string, cause?: unknown);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
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 };
|
|
516
|
+
export { AppCredentials, AuthTokenSnapshot, DEFAULT_APP_CREDENTIALS, LocalStorageTokenStorage, MemoryTokenStorage, type OtpChallenge, type OtpCleanupData, type OtpCompleteInput, type OtpCompleteResult, type OtpFlow, 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 };
|