@tenxyte/core 0.9.2 → 0.9.4
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/LICENSE +21 -0
- package/README.md +53 -8
- package/dist/index.cjs +35 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -8
- package/dist/index.d.ts +29 -8
- package/dist/index.js +35 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tenxyte
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -88,6 +88,10 @@ const tx = new TenxyteClient({
|
|
|
88
88
|
|
|
89
89
|
// Optional — override auto-detected device info
|
|
90
90
|
deviceInfoOverride: { app_name: 'MyApp', app_version: '2.0.0' },
|
|
91
|
+
|
|
92
|
+
// Optional — cookie-based refresh token transport (default: false)
|
|
93
|
+
// Enable when backend has TENXYTE_REFRESH_TOKEN_COOKIE_ENABLED=True
|
|
94
|
+
cookieMode: false,
|
|
91
95
|
});
|
|
92
96
|
```
|
|
93
97
|
|
|
@@ -128,9 +132,16 @@ const tokens = await tx.auth.verifyMagicLink(urlToken);
|
|
|
128
132
|
|
|
129
133
|
// Social OAuth2
|
|
130
134
|
const tokens = await tx.auth.loginWithSocial('google', { id_token: 'jwt...' });
|
|
131
|
-
const tokens = await tx.auth.handleSocialCallback('github', 'auth_code', 'https://myapp.com/cb');
|
|
132
135
|
|
|
133
|
-
//
|
|
136
|
+
// Social OAuth2 with PKCE (RFC 7636)
|
|
137
|
+
const tokens = await tx.auth.loginWithSocial('google', {
|
|
138
|
+
code: 'auth_code',
|
|
139
|
+
redirect_uri: 'https://myapp.com/cb',
|
|
140
|
+
code_verifier: 'pkce_verifier_string',
|
|
141
|
+
});
|
|
142
|
+
const tokens = await tx.auth.handleSocialCallback('github', 'auth_code', 'https://myapp.com/cb', 'pkce_verifier');
|
|
143
|
+
|
|
144
|
+
// Session management (refreshToken param is optional in cookie mode)
|
|
134
145
|
await tx.auth.logout('refresh_token_value');
|
|
135
146
|
await tx.auth.logoutAll();
|
|
136
147
|
await tx.auth.refreshToken('refresh_token_value');
|
|
@@ -384,16 +395,49 @@ const state = await tx.getState();
|
|
|
384
395
|
|
|
385
396
|
---
|
|
386
397
|
|
|
387
|
-
## Migration Guide: v0.9 →
|
|
398
|
+
## Migration Guide: v0.9 → v0.10
|
|
399
|
+
|
|
400
|
+
### New Features in v0.10
|
|
401
|
+
|
|
402
|
+
- **Cookie-based refresh tokens** — New `cookieMode` config option. When enabled, the SDK uses `credentials: 'include'` for refresh/logout requests and does not require a stored refresh token for silent refresh.
|
|
403
|
+
- **PKCE support** — `code_verifier` parameter added to `SocialLoginRequest` and `handleSocialCallback()` for RFC 7636 compliance.
|
|
404
|
+
- **Expanded error codes** — `TenxyteErrorCode` now includes all backend error codes: `MISSING_REFRESH_TOKEN`, `INVALID_REDIRECT_URI`, `PASSWORD_BREACHED`, `PASSWORD_REUSED`, `WEBAUTHN_*`, `LINK_EXPIRED`, `2FA_ALREADY_ENABLED`, and more.
|
|
405
|
+
- **Optional refresh token in responses** — `TokenPair.refresh_token` is now optional (absent when cookie mode is enabled on the backend).
|
|
406
|
+
|
|
407
|
+
### Breaking Changes
|
|
408
|
+
|
|
409
|
+
1. **`TokenPair.refresh_token` is now optional** — If you access `tokens.refresh_token` without a null check, add one:
|
|
410
|
+
```typescript
|
|
411
|
+
if (tokens.refresh_token) {
|
|
412
|
+
// Store or use the refresh token
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
2. **`logout()` and `refreshToken()` parameters are now optional** — In cookie mode, you can call them without arguments:
|
|
417
|
+
```typescript
|
|
418
|
+
// Cookie mode (refresh token is in HttpOnly cookie)
|
|
419
|
+
await tx.auth.logout();
|
|
420
|
+
await tx.auth.refreshToken();
|
|
421
|
+
|
|
422
|
+
// Classic mode (still works)
|
|
423
|
+
await tx.auth.logout('refresh_token_value');
|
|
424
|
+
await tx.auth.refreshToken('refresh_token_value');
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
3. **`handleSocialCallback()` now accepts an optional 4th parameter** (`codeVerifier`).
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Migration Guide: v0.8 → v0.9
|
|
388
432
|
|
|
389
433
|
### Breaking Changes
|
|
390
434
|
|
|
391
435
|
1. **Constructor signature changed** — The client now accepts a `TenxyteClientConfig` object:
|
|
392
436
|
```typescript
|
|
393
|
-
// Before (v0.
|
|
437
|
+
// Before (v0.8)
|
|
394
438
|
const tx = new TenxyteClient({ baseUrl: '...', headers: { ... } });
|
|
395
439
|
|
|
396
|
-
// After (
|
|
440
|
+
// After (v0.9) — same, but new options available
|
|
397
441
|
const tx = new TenxyteClient({
|
|
398
442
|
baseUrl: '...',
|
|
399
443
|
headers: { ... },
|
|
@@ -405,10 +449,10 @@ const state = await tx.getState();
|
|
|
405
449
|
|
|
406
450
|
2. **`loginWithEmail` now requires `device_info`**:
|
|
407
451
|
```typescript
|
|
408
|
-
// Before (v0.
|
|
452
|
+
// Before (v0.8)
|
|
409
453
|
await tx.auth.loginWithEmail({ email, password });
|
|
410
454
|
|
|
411
|
-
// After (
|
|
455
|
+
// After (v0.9)
|
|
412
456
|
await tx.auth.loginWithEmail({ email, password, device_info: '' });
|
|
413
457
|
```
|
|
414
458
|
|
|
@@ -427,7 +471,7 @@ const state = await tx.getState();
|
|
|
427
471
|
|
|
428
472
|
6. **`register()` return type changed** — Now returns `RegisterResponse` (may include tokens if auto-login is enabled).
|
|
429
473
|
|
|
430
|
-
### New Features in
|
|
474
|
+
### New Features in v0.9
|
|
431
475
|
|
|
432
476
|
- Auto-refresh interceptor (silent 401 → refresh → retry)
|
|
433
477
|
- Configurable retry with exponential backoff (429/5xx)
|
|
@@ -436,6 +480,7 @@ const state = await tx.getState();
|
|
|
436
480
|
- High-level helpers (`isAuthenticated`, `getCurrentUser`, `isTokenExpired`)
|
|
437
481
|
- `getState()` for framework wrapper integration
|
|
438
482
|
- EventEmitter for reactive state (`session:expired`, `token:refreshed`, etc.)
|
|
483
|
+
- WebAuthn / Passkeys (FIDO2) support
|
|
439
484
|
|
|
440
485
|
---
|
|
441
486
|
|
package/dist/index.cjs
CHANGED
|
@@ -174,7 +174,8 @@ function resolveConfig(config) {
|
|
|
174
174
|
logger: config.logger ?? NOOP_LOGGER,
|
|
175
175
|
logLevel: config.logLevel ?? "silent",
|
|
176
176
|
deviceInfoOverride: config.deviceInfoOverride,
|
|
177
|
-
retryConfig: config.retryConfig
|
|
177
|
+
retryConfig: config.retryConfig,
|
|
178
|
+
cookieMode: config.cookieMode ?? false
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
181
|
|
|
@@ -396,7 +397,7 @@ function createAuthInterceptor(storage, context) {
|
|
|
396
397
|
return { ...request, headers };
|
|
397
398
|
};
|
|
398
399
|
}
|
|
399
|
-
function createRefreshInterceptor(client, storage, onSessionExpired, onTokenRefreshed) {
|
|
400
|
+
function createRefreshInterceptor(client, storage, onSessionExpired, onTokenRefreshed, cookieMode = false) {
|
|
400
401
|
let isRefreshing = false;
|
|
401
402
|
let refreshQueue = [];
|
|
402
403
|
const processQueue = (error, token = null) => {
|
|
@@ -406,7 +407,7 @@ function createRefreshInterceptor(client, storage, onSessionExpired, onTokenRefr
|
|
|
406
407
|
return async (response, request) => {
|
|
407
408
|
if (response.status === 401 && !request.url.includes("/auth/refresh") && !request.url.includes("/auth/login")) {
|
|
408
409
|
const refreshToken = await storage.getItem("tx_refresh");
|
|
409
|
-
if (!refreshToken) {
|
|
410
|
+
if (!refreshToken && !cookieMode) {
|
|
410
411
|
onSessionExpired();
|
|
411
412
|
return response;
|
|
412
413
|
}
|
|
@@ -424,10 +425,15 @@ function createRefreshInterceptor(client, storage, onSessionExpired, onTokenRefr
|
|
|
424
425
|
}
|
|
425
426
|
isRefreshing = true;
|
|
426
427
|
try {
|
|
428
|
+
const refreshBody = {};
|
|
429
|
+
if (refreshToken) {
|
|
430
|
+
refreshBody.refresh_token = refreshToken;
|
|
431
|
+
}
|
|
427
432
|
const refreshResponse = await fetch(`${client["baseUrl"]}/auth/refresh/`, {
|
|
428
433
|
method: "POST",
|
|
429
434
|
headers: { "Content-Type": "application/json" },
|
|
430
|
-
body: JSON.stringify(
|
|
435
|
+
body: JSON.stringify(refreshBody),
|
|
436
|
+
...cookieMode ? { credentials: "include" } : {}
|
|
431
437
|
});
|
|
432
438
|
if (!refreshResponse.ok) {
|
|
433
439
|
throw new Error("Refresh failed");
|
|
@@ -585,10 +591,16 @@ var AuthModule = class {
|
|
|
585
591
|
/**
|
|
586
592
|
* Logout from the current session.
|
|
587
593
|
* Informs the backend to immediately revoke the specified refresh token.
|
|
588
|
-
*
|
|
594
|
+
* When cookie mode is enabled, the refresh token parameter is optional —
|
|
595
|
+
* the server reads it from the HttpOnly cookie and clears it via Set-Cookie.
|
|
596
|
+
* @param refreshToken - The refresh token to revoke (optional in cookie mode).
|
|
589
597
|
*/
|
|
590
598
|
async logout(refreshToken) {
|
|
591
|
-
|
|
599
|
+
const body = {};
|
|
600
|
+
if (refreshToken) {
|
|
601
|
+
body.refresh_token = refreshToken;
|
|
602
|
+
}
|
|
603
|
+
await this.client.post("/api/v1/auth/logout/", body);
|
|
592
604
|
await this.clearTokens();
|
|
593
605
|
}
|
|
594
606
|
/**
|
|
@@ -602,11 +614,17 @@ var AuthModule = class {
|
|
|
602
614
|
/**
|
|
603
615
|
* Manually refresh the access token using a valid refresh token.
|
|
604
616
|
* The refresh token is automatically rotated for improved security.
|
|
605
|
-
*
|
|
617
|
+
* When cookie mode is enabled, the refresh token parameter is optional —
|
|
618
|
+
* the server reads it from the HttpOnly cookie.
|
|
619
|
+
* @param refreshToken - The current refresh token (optional in cookie mode).
|
|
606
620
|
* @returns A new token pair (access + rotated refresh).
|
|
607
621
|
*/
|
|
608
622
|
async refreshToken(refreshToken) {
|
|
609
|
-
const
|
|
623
|
+
const body = {};
|
|
624
|
+
if (refreshToken) {
|
|
625
|
+
body.refresh_token = refreshToken;
|
|
626
|
+
}
|
|
627
|
+
const tokens = await this.client.post("/api/v1/auth/refresh/", body);
|
|
610
628
|
return this.persistTokens(tokens);
|
|
611
629
|
}
|
|
612
630
|
/**
|
|
@@ -641,11 +659,16 @@ var AuthModule = class {
|
|
|
641
659
|
* @param provider - The OAuth provider ('google', 'github', etc.)
|
|
642
660
|
* @param code - The authorization code retrieved from the query string parameters.
|
|
643
661
|
* @param redirectUri - The original redirect URI that was requested.
|
|
662
|
+
* @param codeVerifier - Optional PKCE code verifier (RFC 7636).
|
|
644
663
|
* @returns An active session token pair after successful code exchange.
|
|
645
664
|
*/
|
|
646
|
-
async handleSocialCallback(provider, code, redirectUri) {
|
|
665
|
+
async handleSocialCallback(provider, code, redirectUri, codeVerifier) {
|
|
666
|
+
const params = { code, redirect_uri: redirectUri };
|
|
667
|
+
if (codeVerifier) {
|
|
668
|
+
params.code_verifier = codeVerifier;
|
|
669
|
+
}
|
|
647
670
|
const tokens = await this.client.get(`/api/v1/auth/social/${provider}/callback/`, {
|
|
648
|
-
params
|
|
671
|
+
params
|
|
649
672
|
});
|
|
650
673
|
return this.persistTokens(tokens);
|
|
651
674
|
}
|
|
@@ -1748,7 +1771,8 @@ var TenxyteClient = class {
|
|
|
1748
1771
|
this.rbac.setToken(accessToken);
|
|
1749
1772
|
this.emit("token:refreshed", { accessToken });
|
|
1750
1773
|
this.emit("token:stored", { accessToken, refreshToken });
|
|
1751
|
-
}
|
|
1774
|
+
},
|
|
1775
|
+
this.config.cookieMode
|
|
1752
1776
|
)
|
|
1753
1777
|
);
|
|
1754
1778
|
}
|