dexie-cloud-addon 4.4.3 → 4.4.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/dist/modern/DexieCloudAPI.d.ts +13 -0
- package/dist/modern/authentication/exchangeOAuthCode.d.ts +2 -0
- package/dist/modern/authentication/interactWithUser.d.ts +1 -1
- package/dist/modern/dexie-cloud-addon.js +175 -75
- package/dist/modern/dexie-cloud-addon.js.map +1 -1
- package/dist/modern/dexie-cloud-addon.min.js +1 -1
- package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
- package/dist/modern/errors/PolicyRejectionError.d.ts +23 -0
- package/dist/modern/extend-dexie-interface.d.ts +1 -1
- package/dist/modern/service-worker.js +175 -75
- package/dist/modern/service-worker.js.map +1 -1
- package/dist/modern/service-worker.min.js +1 -1
- package/dist/modern/service-worker.min.js.map +1 -1
- package/dist/modern/types/DXCAlert.d.ts +1 -1
- package/dist/umd/dexie-cloud-addon.js +176 -76
- package/dist/umd/dexie-cloud-addon.js.map +1 -1
- package/dist/umd/dexie-cloud-addon.min.js +1 -1
- package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
- package/dist/umd/service-worker.js +176 -76
- package/dist/umd/service-worker.js.map +1 -1
- package/dist/umd/service-worker.min.js +1 -1
- package/dist/umd/service-worker.min.js.map +1 -1
- package/package.json +2 -2
|
@@ -32,6 +32,19 @@ export interface LoginHints {
|
|
|
32
32
|
oauthCode?: string;
|
|
33
33
|
/** Optional redirect path (relative or absolute) to use for OAuth redirect URI. */
|
|
34
34
|
redirectPath?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Optional login intent hint.
|
|
37
|
+
*
|
|
38
|
+
* - `"login"`: Only existing users are accepted. Unknown users always get
|
|
39
|
+
* USER_NOT_REGISTERED regardless of the database user policy.
|
|
40
|
+
* - `"register"`: New-user registration is intended. Unknown users that would
|
|
41
|
+
* normally get USER_NOT_REGISTERED will instead get
|
|
42
|
+
* USER_NOT_ACCEPTED (i.e. the policy blocked them, not the
|
|
43
|
+
* fact that registration isn't supported here).
|
|
44
|
+
* - `undefined`: Default behaviour — the server returns whatever code the
|
|
45
|
+
* user policy dictates.
|
|
46
|
+
*/
|
|
47
|
+
intent?: 'login' | 'register';
|
|
35
48
|
}
|
|
36
49
|
export interface DexieCloudAPI {
|
|
37
50
|
version: string;
|
|
@@ -9,6 +9,8 @@ export interface ExchangeOAuthCodeOptions {
|
|
|
9
9
|
publicKey: string;
|
|
10
10
|
/** Requested scopes (defaults to ['ACCESS_DB']) */
|
|
11
11
|
scopes?: string[];
|
|
12
|
+
/** Optional login intent — see LoginHints.intent for semantics. */
|
|
13
|
+
intent?: 'login' | 'register';
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
16
|
* Exchanges a Dexie Cloud authorization code for access and refresh tokens.
|
|
@@ -17,7 +17,7 @@ export declare function interactWithUser<T extends DXCUserInteractionRequest>(us
|
|
|
17
17
|
[P in keyof T['fields']]: string;
|
|
18
18
|
}>;
|
|
19
19
|
export declare function alertUser(userInteraction: BehaviorSubject<DXCUserInteraction | undefined>, title: string, ...alerts: DXCAlert[]): Promise<{}>;
|
|
20
|
-
export declare function promptForEmail(userInteraction: BehaviorSubject<DXCUserInteraction | undefined>, title: string, emailHint?: string): Promise<string>;
|
|
20
|
+
export declare function promptForEmail(userInteraction: BehaviorSubject<DXCUserInteraction | undefined>, title: string, emailHint?: string, initialAlert?: DXCAlert): Promise<string>;
|
|
21
21
|
export declare function promptForOTP(userInteraction: BehaviorSubject<DXCUserInteraction | undefined>, email: string, alert?: DXCAlert): Promise<string>;
|
|
22
22
|
export declare function confirmLogout(userInteraction: BehaviorSubject<DXCUserInteraction | undefined>, currentUserId: string, numUnsyncedChanges: number): Promise<boolean>;
|
|
23
23
|
/** Result from provider selection prompt */
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ==========================================================================
|
|
10
10
|
*
|
|
11
|
-
* Version 4.4.
|
|
11
|
+
* Version 4.4.4, Wed Mar 25 2026
|
|
12
12
|
*
|
|
13
13
|
* https://dexie.org
|
|
14
14
|
*
|
|
@@ -1858,9 +1858,10 @@ function alertUser(userInteraction, title, ...alerts) {
|
|
|
1858
1858
|
cancelLabel: null,
|
|
1859
1859
|
});
|
|
1860
1860
|
}
|
|
1861
|
-
function promptForEmail(userInteraction, title, emailHint) {
|
|
1861
|
+
function promptForEmail(userInteraction, title, emailHint, initialAlert) {
|
|
1862
1862
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1863
1863
|
let email = emailHint || '';
|
|
1864
|
+
let firstPrompt = true;
|
|
1864
1865
|
// Regular expression for email validation
|
|
1865
1866
|
// ^[\w-+.]+@([\w-]+\.)+[\w-]{2,10}(\sas\s[\w-+.]+@([\w-]+\.)+[\w-]{2,10})?$
|
|
1866
1867
|
//
|
|
@@ -1883,19 +1884,21 @@ function promptForEmail(userInteraction, title, emailHint) {
|
|
|
1883
1884
|
// and GLOBAL_WRITE permissions on the database. The email will be checked on the server before
|
|
1884
1885
|
// allowing it and giving out a token for email2, using the OTP sent to email1.
|
|
1885
1886
|
while (!email || !/^[\w-+.]+@([\w-]+\.)+[\w-]{2,10}(\sas\s[\w-+.]+@([\w-]+\.)+[\w-]{2,10})?$/.test(email)) {
|
|
1887
|
+
const alerts = [];
|
|
1888
|
+
if (firstPrompt && initialAlert)
|
|
1889
|
+
alerts.push(initialAlert);
|
|
1890
|
+
if (email)
|
|
1891
|
+
alerts.push({
|
|
1892
|
+
type: 'error',
|
|
1893
|
+
messageCode: 'INVALID_EMAIL',
|
|
1894
|
+
message: 'Please enter a valid email address',
|
|
1895
|
+
messageParams: {},
|
|
1896
|
+
});
|
|
1897
|
+
firstPrompt = false;
|
|
1886
1898
|
email = (yield interactWithUser(userInteraction, {
|
|
1887
1899
|
type: 'email',
|
|
1888
1900
|
title,
|
|
1889
|
-
alerts
|
|
1890
|
-
? [
|
|
1891
|
-
{
|
|
1892
|
-
type: 'error',
|
|
1893
|
-
messageCode: 'INVALID_EMAIL',
|
|
1894
|
-
message: 'Please enter a valid email address',
|
|
1895
|
-
messageParams: {},
|
|
1896
|
-
},
|
|
1897
|
-
]
|
|
1898
|
-
: [],
|
|
1901
|
+
alerts,
|
|
1899
1902
|
fields: {
|
|
1900
1903
|
email: {
|
|
1901
1904
|
type: 'email',
|
|
@@ -2038,6 +2041,29 @@ class OAuthRedirectError extends Error {
|
|
|
2038
2041
|
}
|
|
2039
2042
|
}
|
|
2040
2043
|
|
|
2044
|
+
/** Thrown when the server rejects a user due to a policy rule.
|
|
2045
|
+
*
|
|
2046
|
+
* Unlike a generic 403, this error carries a machine-readable `code` so that
|
|
2047
|
+
* the addon can convert it into a DXCUserInteraction challenge rather than
|
|
2048
|
+
* simply throwing.
|
|
2049
|
+
*/
|
|
2050
|
+
class PolicyRejectionError extends Error {
|
|
2051
|
+
constructor(body) {
|
|
2052
|
+
super(body.message);
|
|
2053
|
+
this.code = body.code;
|
|
2054
|
+
}
|
|
2055
|
+
get name() {
|
|
2056
|
+
return 'PolicyRejectionError';
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
/** Returns true when a plain fetch Response contains a structured PolicyError body. */
|
|
2060
|
+
function isPolicyErrorBody(value) {
|
|
2061
|
+
return (typeof value === 'object' &&
|
|
2062
|
+
value !== null &&
|
|
2063
|
+
typeof value.code === 'string' &&
|
|
2064
|
+
typeof value.message === 'string');
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2041
2067
|
const SECONDS = 1000;
|
|
2042
2068
|
const MINUTES = 60 * SECONDS;
|
|
2043
2069
|
|
|
@@ -2212,6 +2238,10 @@ function userAuthenticate(context, fetchToken, userInteraction, hints) {
|
|
|
2212
2238
|
if (error instanceof OAuthRedirectError || (error === null || error === void 0 ? void 0 : error.name) === 'OAuthRedirectError') {
|
|
2213
2239
|
throw error; // Re-throw without logging
|
|
2214
2240
|
}
|
|
2241
|
+
// Policy rejections have already been shown to the user as a challenge
|
|
2242
|
+
if (error instanceof PolicyRejectionError || (error === null || error === void 0 ? void 0 : error.name) === 'PolicyRejectionError') {
|
|
2243
|
+
throw error;
|
|
2244
|
+
}
|
|
2215
2245
|
if (error instanceof TokenErrorResponseError) {
|
|
2216
2246
|
yield alertUser(userInteraction, error.title, {
|
|
2217
2247
|
type: 'error',
|
|
@@ -2410,13 +2440,8 @@ class OAuthError extends Error {
|
|
|
2410
2440
|
*/
|
|
2411
2441
|
function exchangeOAuthCode(options) {
|
|
2412
2442
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2413
|
-
const { databaseUrl, code, publicKey, scopes = ['ACCESS_DB'] } = options;
|
|
2414
|
-
const tokenRequest = {
|
|
2415
|
-
grant_type: 'authorization_code',
|
|
2416
|
-
code,
|
|
2417
|
-
public_key: publicKey,
|
|
2418
|
-
scopes,
|
|
2419
|
-
};
|
|
2443
|
+
const { databaseUrl, code, publicKey, scopes = ['ACCESS_DB'], intent } = options;
|
|
2444
|
+
const tokenRequest = Object.assign({ grant_type: 'authorization_code', code, public_key: publicKey, scopes }, (intent !== undefined ? { intent } : {}));
|
|
2420
2445
|
try {
|
|
2421
2446
|
const res = yield fetch(`${databaseUrl}/token`, {
|
|
2422
2447
|
method: 'POST',
|
|
@@ -2427,6 +2452,20 @@ function exchangeOAuthCode(options) {
|
|
|
2427
2452
|
if (!res.ok) {
|
|
2428
2453
|
// Read body once as text to avoid stream consumption issues
|
|
2429
2454
|
const bodyText = yield res.text().catch(() => res.statusText);
|
|
2455
|
+
// Check for structured policy rejection (403 with JSON body)
|
|
2456
|
+
if (res.status === 403) {
|
|
2457
|
+
try {
|
|
2458
|
+
const body = JSON.parse(bodyText);
|
|
2459
|
+
if (isPolicyErrorBody(body)) {
|
|
2460
|
+
throw new PolicyRejectionError(body);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
catch (e) {
|
|
2464
|
+
if (e instanceof PolicyRejectionError)
|
|
2465
|
+
throw e;
|
|
2466
|
+
// Fall through to generic error
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2430
2469
|
if (res.status === 400 || res.status === 401) {
|
|
2431
2470
|
// Try to parse error response as JSON
|
|
2432
2471
|
try {
|
|
@@ -2562,32 +2601,59 @@ function startOAuthRedirect(options) {
|
|
|
2562
2601
|
|
|
2563
2602
|
function otpFetchTokenCallback(db) {
|
|
2564
2603
|
const { userInteraction } = db.cloud;
|
|
2565
|
-
|
|
2566
|
-
|
|
2604
|
+
/**
|
|
2605
|
+
* Core authentication function.
|
|
2606
|
+
*
|
|
2607
|
+
* @param public_key - RSA public key PEM for the session
|
|
2608
|
+
* @param hints - Optional login hints from the caller
|
|
2609
|
+
* @param policyAlert - When set, a previous attempt was rejected by a server
|
|
2610
|
+
* policy rule. The alert is injected into the first
|
|
2611
|
+
* interactive prompt so the user sees why they were
|
|
2612
|
+
* rejected without changing any other flow logic.
|
|
2613
|
+
*/
|
|
2614
|
+
function otpAuthenticate(_a, policyAlert_1) {
|
|
2615
|
+
return __awaiter(this, arguments, void 0, function* ({ public_key, hints }, policyAlert) {
|
|
2567
2616
|
var _b, _c;
|
|
2568
2617
|
let tokenRequest;
|
|
2569
2618
|
const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
|
|
2570
2619
|
if (!url)
|
|
2571
2620
|
throw new Error(`No database URL given.`);
|
|
2621
|
+
const intent = hints === null || hints === void 0 ? void 0 : hints.intent;
|
|
2622
|
+
// ── Non-interactive paths ──────────────────────────────────────────────
|
|
2623
|
+
// These paths POST directly without prompting the user. If a policyAlert
|
|
2624
|
+
// exists (from a previous rejected attempt), show it with a message-alert
|
|
2625
|
+
// before proceeding so the user understands what happened.
|
|
2572
2626
|
// Handle OAuth code exchange (from redirect/deep link flows)
|
|
2573
2627
|
if ((hints === null || hints === void 0 ? void 0 : hints.oauthCode) && hints.provider) {
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2628
|
+
try {
|
|
2629
|
+
return yield exchangeOAuthCode({
|
|
2630
|
+
databaseUrl: url,
|
|
2631
|
+
code: hints.oauthCode,
|
|
2632
|
+
publicKey: public_key,
|
|
2633
|
+
scopes: ['ACCESS_DB'],
|
|
2634
|
+
intent,
|
|
2635
|
+
});
|
|
2636
|
+
}
|
|
2637
|
+
catch (err) {
|
|
2638
|
+
if (err instanceof PolicyRejectionError) {
|
|
2639
|
+
return yield otpAuthenticate({ public_key, hints: undefined }, toPolicyAlert(err));
|
|
2640
|
+
}
|
|
2641
|
+
throw err;
|
|
2642
|
+
}
|
|
2580
2643
|
}
|
|
2581
|
-
// Handle OAuth provider login via redirect
|
|
2644
|
+
// Handle OAuth provider login via redirect (programmatic, no interaction)
|
|
2582
2645
|
if (hints === null || hints === void 0 ? void 0 : hints.provider) {
|
|
2646
|
+
if (policyAlert) {
|
|
2647
|
+
// A previous OAuth attempt was rejected. Fall through to the
|
|
2648
|
+
// interactive flow — policyAlert will be shown inside the prompt.
|
|
2649
|
+
return yield otpAuthenticate({ public_key, hints: undefined }, policyAlert);
|
|
2650
|
+
}
|
|
2583
2651
|
let resolvedRedirectUri = undefined;
|
|
2584
2652
|
if (hints.redirectPath) {
|
|
2585
|
-
// If redirectPath is absolute, use as is. If relative, resolve against current location
|
|
2586
2653
|
if (/^https?:\/\//i.test(hints.redirectPath)) {
|
|
2587
2654
|
resolvedRedirectUri = hints.redirectPath;
|
|
2588
2655
|
}
|
|
2589
2656
|
else if (typeof window !== 'undefined' && window.location) {
|
|
2590
|
-
// Use URL constructor to resolve relative path
|
|
2591
2657
|
resolvedRedirectUri = new URL(hints.redirectPath, window.location.href).toString();
|
|
2592
2658
|
}
|
|
2593
2659
|
else if (typeof location !== 'undefined' && location.href) {
|
|
@@ -2595,23 +2661,27 @@ function otpFetchTokenCallback(db) {
|
|
|
2595
2661
|
}
|
|
2596
2662
|
}
|
|
2597
2663
|
initiateOAuthRedirect(db, hints.provider, resolvedRedirectUri);
|
|
2598
|
-
// This function never returns - page navigates away
|
|
2599
2664
|
throw new OAuthRedirectError(hints.provider);
|
|
2600
2665
|
}
|
|
2666
|
+
// ── Interactive paths ──────────────────────────────────────────────────
|
|
2667
|
+
// policyAlert (if set) is injected into the first prompt so the user sees
|
|
2668
|
+
// it alongside the normal auth UI — no separate error screen needed.
|
|
2601
2669
|
if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
|
|
2602
|
-
const demo_user = yield promptForEmail(userInteraction, 'Enter a demo user email', (hints === null || hints === void 0 ? void 0 : hints.email) || (hints === null || hints === void 0 ? void 0 : hints.userId));
|
|
2670
|
+
const demo_user = yield promptForEmail(userInteraction, 'Enter a demo user email', (hints === null || hints === void 0 ? void 0 : hints.email) || (hints === null || hints === void 0 ? void 0 : hints.userId), policyAlert);
|
|
2603
2671
|
tokenRequest = {
|
|
2604
2672
|
demo_user,
|
|
2605
2673
|
grant_type: 'demo',
|
|
2606
2674
|
scopes: ['ACCESS_DB'],
|
|
2607
|
-
public_key
|
|
2675
|
+
public_key,
|
|
2608
2676
|
};
|
|
2609
2677
|
}
|
|
2610
2678
|
else if ((hints === null || hints === void 0 ? void 0 : hints.otpId) && hints.otp) {
|
|
2611
|
-
//
|
|
2612
|
-
//
|
|
2613
|
-
|
|
2614
|
-
|
|
2679
|
+
// Magic-link flow: OTP already supplied by the caller (e.g. from email).
|
|
2680
|
+
// No interaction — show alert as a plain message if there is one.
|
|
2681
|
+
if (policyAlert) {
|
|
2682
|
+
yield alertUser(userInteraction, 'Access Denied', policyAlert);
|
|
2683
|
+
return yield otpAuthenticate({ public_key, hints: undefined }, policyAlert);
|
|
2684
|
+
}
|
|
2615
2685
|
tokenRequest = {
|
|
2616
2686
|
grant_type: 'otp',
|
|
2617
2687
|
otp_id: hints.otpId,
|
|
@@ -2621,56 +2691,52 @@ function otpFetchTokenCallback(db) {
|
|
|
2621
2691
|
};
|
|
2622
2692
|
}
|
|
2623
2693
|
else if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'otp' || (hints === null || hints === void 0 ? void 0 : hints.email)) {
|
|
2624
|
-
//
|
|
2625
|
-
const email = (hints === null || hints === void 0 ? void 0 : hints.email) ||
|
|
2694
|
+
// Caller explicitly requested OTP — skip provider selection.
|
|
2695
|
+
const email = (hints === null || hints === void 0 ? void 0 : hints.email) ||
|
|
2696
|
+
(yield promptForEmail(userInteraction, 'Enter email address', undefined, policyAlert));
|
|
2626
2697
|
if (/@demo.local$/.test(email)) {
|
|
2627
2698
|
tokenRequest = {
|
|
2628
2699
|
demo_user: email,
|
|
2629
2700
|
grant_type: 'demo',
|
|
2630
2701
|
scopes: ['ACCESS_DB'],
|
|
2631
|
-
public_key
|
|
2702
|
+
public_key,
|
|
2632
2703
|
};
|
|
2633
2704
|
}
|
|
2634
2705
|
else {
|
|
2635
|
-
tokenRequest = {
|
|
2636
|
-
email,
|
|
2637
|
-
grant_type: 'otp',
|
|
2638
|
-
scopes: ['ACCESS_DB'],
|
|
2639
|
-
};
|
|
2706
|
+
tokenRequest = Object.assign({ email, grant_type: 'otp', scopes: ['ACCESS_DB'] }, (intent !== undefined ? { intent } : {}));
|
|
2640
2707
|
}
|
|
2641
2708
|
}
|
|
2642
2709
|
else {
|
|
2643
|
-
//
|
|
2710
|
+
// Default path: check for OAuth providers, then fall back to OTP.
|
|
2644
2711
|
const socialAuthEnabled = ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.socialAuth) !== false;
|
|
2645
2712
|
const authProviders = yield fetchAuthProviders(url, socialAuthEnabled);
|
|
2646
|
-
// If we have OAuth providers available, prompt for selection
|
|
2647
2713
|
if (authProviders.providers.length > 0) {
|
|
2648
|
-
const
|
|
2714
|
+
const providerAlerts = policyAlert ? [policyAlert] : [];
|
|
2715
|
+
const selection = yield promptForProvider(userInteraction, authProviders.providers, authProviders.otpEnabled, 'Sign in', providerAlerts);
|
|
2649
2716
|
if (selection.type === 'provider') {
|
|
2650
|
-
// User selected an OAuth provider - initiate redirect
|
|
2651
2717
|
initiateOAuthRedirect(db, selection.provider);
|
|
2652
|
-
// This function never returns - page navigates away
|
|
2653
2718
|
throw new OAuthRedirectError(selection.provider);
|
|
2654
2719
|
}
|
|
2655
|
-
// User chose OTP
|
|
2720
|
+
// User chose OTP — fall through to email prompt (no policyAlert here;
|
|
2721
|
+
// it was already shown in the provider prompt above).
|
|
2656
2722
|
}
|
|
2657
|
-
const email = yield promptForEmail(userInteraction, 'Enter email address', hints === null || hints === void 0 ? void 0 : hints.email
|
|
2723
|
+
const email = yield promptForEmail(userInteraction, 'Enter email address', hints === null || hints === void 0 ? void 0 : hints.email,
|
|
2724
|
+
// Show policyAlert in email prompt only if there were no providers
|
|
2725
|
+
// (otherwise it was already shown in the provider selection above).
|
|
2726
|
+
authProviders.providers.length === 0 ? policyAlert : undefined);
|
|
2658
2727
|
if (/@demo.local$/.test(email)) {
|
|
2659
2728
|
tokenRequest = {
|
|
2660
2729
|
demo_user: email,
|
|
2661
2730
|
grant_type: 'demo',
|
|
2662
2731
|
scopes: ['ACCESS_DB'],
|
|
2663
|
-
public_key
|
|
2732
|
+
public_key,
|
|
2664
2733
|
};
|
|
2665
2734
|
}
|
|
2666
2735
|
else {
|
|
2667
|
-
tokenRequest = {
|
|
2668
|
-
email,
|
|
2669
|
-
grant_type: 'otp',
|
|
2670
|
-
scopes: ['ACCESS_DB'],
|
|
2671
|
-
};
|
|
2736
|
+
tokenRequest = Object.assign({ email, grant_type: 'otp', scopes: ['ACCESS_DB'] }, (intent !== undefined ? { intent } : {}));
|
|
2672
2737
|
}
|
|
2673
2738
|
}
|
|
2739
|
+
// ── POST /token (step 1) ───────────────────────────────────────────────
|
|
2674
2740
|
const res1 = yield fetch(`${url}/token`, {
|
|
2675
2741
|
body: JSON.stringify(tokenRequest),
|
|
2676
2742
|
method: 'post',
|
|
@@ -2678,19 +2744,22 @@ function otpFetchTokenCallback(db) {
|
|
|
2678
2744
|
mode: 'cors',
|
|
2679
2745
|
});
|
|
2680
2746
|
if (res1.status !== 200) {
|
|
2747
|
+
const alert = yield tryParsePolicyAlert(res1);
|
|
2748
|
+
if (alert) {
|
|
2749
|
+
// Policy rejection — restart the flow with the error injected.
|
|
2750
|
+
return yield otpAuthenticate({ public_key, hints: undefined }, alert);
|
|
2751
|
+
}
|
|
2681
2752
|
const errMsg = yield res1.text();
|
|
2682
|
-
yield alertUser(userInteraction,
|
|
2753
|
+
yield alertUser(userInteraction, 'Token request failed', {
|
|
2683
2754
|
type: 'error',
|
|
2684
2755
|
messageCode: 'GENERIC_ERROR',
|
|
2685
2756
|
message: errMsg,
|
|
2686
|
-
messageParams: {}
|
|
2757
|
+
messageParams: {},
|
|
2687
2758
|
}).catch(() => { });
|
|
2688
2759
|
throw new HttpError(res1, errMsg);
|
|
2689
2760
|
}
|
|
2690
2761
|
const response = yield res1.json();
|
|
2691
2762
|
if (response.type === 'tokens' || response.type === 'error') {
|
|
2692
|
-
// Demo user request can get a "tokens" response right away
|
|
2693
|
-
// Error can also be returned right away.
|
|
2694
2763
|
return response;
|
|
2695
2764
|
}
|
|
2696
2765
|
else if (tokenRequest.grant_type === 'otp' && 'email' in tokenRequest) {
|
|
@@ -2698,6 +2767,7 @@ function otpFetchTokenCallback(db) {
|
|
|
2698
2767
|
throw new Error(`Unexpected response from ${url}/token`);
|
|
2699
2768
|
const otp = yield promptForOTP(userInteraction, tokenRequest.email);
|
|
2700
2769
|
const tokenRequest2 = Object.assign(Object.assign({}, tokenRequest), { otp: otp || '', otp_id: response.otp_id, public_key });
|
|
2770
|
+
// ── POST /token (step 2: OTP verification) ─────────────────────────
|
|
2701
2771
|
let res2 = yield fetch(`${url}/token`, {
|
|
2702
2772
|
body: JSON.stringify(tokenRequest2),
|
|
2703
2773
|
method: 'post',
|
|
@@ -2710,7 +2780,7 @@ function otpFetchTokenCallback(db) {
|
|
|
2710
2780
|
type: 'error',
|
|
2711
2781
|
messageCode: 'INVALID_OTP',
|
|
2712
2782
|
message: errorText,
|
|
2713
|
-
messageParams: {}
|
|
2783
|
+
messageParams: {},
|
|
2714
2784
|
});
|
|
2715
2785
|
res2 = yield fetch(`${url}/token`, {
|
|
2716
2786
|
body: JSON.stringify(tokenRequest2),
|
|
@@ -2720,6 +2790,10 @@ function otpFetchTokenCallback(db) {
|
|
|
2720
2790
|
});
|
|
2721
2791
|
}
|
|
2722
2792
|
if (res2.status !== 200) {
|
|
2793
|
+
const alert = yield tryParsePolicyAlert(res2);
|
|
2794
|
+
if (alert) {
|
|
2795
|
+
return yield otpAuthenticate({ public_key, hints: undefined }, alert);
|
|
2796
|
+
}
|
|
2723
2797
|
const errMsg = yield res2.text();
|
|
2724
2798
|
throw new HttpError(res2, errMsg);
|
|
2725
2799
|
}
|
|
@@ -2730,14 +2804,11 @@ function otpFetchTokenCallback(db) {
|
|
|
2730
2804
|
throw new Error(`Unexpected response from ${url}/token`);
|
|
2731
2805
|
}
|
|
2732
2806
|
});
|
|
2733
|
-
}
|
|
2807
|
+
}
|
|
2808
|
+
return ({ public_key, hints }) => otpAuthenticate({ public_key, hints });
|
|
2734
2809
|
}
|
|
2735
2810
|
/**
|
|
2736
2811
|
* Initiates OAuth login via full page redirect.
|
|
2737
|
-
*
|
|
2738
|
-
* The page will navigate away to the OAuth provider. After authentication,
|
|
2739
|
-
* the user is redirected back with a dxc-auth query parameter that is
|
|
2740
|
-
* automatically detected by db.cloud.configure().
|
|
2741
2812
|
*/
|
|
2742
2813
|
function initiateOAuthRedirect(db, provider, redirectUriOverride) {
|
|
2743
2814
|
var _a, _b;
|
|
@@ -2747,17 +2818,44 @@ function initiateOAuthRedirect(db, provider, redirectUriOverride) {
|
|
|
2747
2818
|
const redirectUri = redirectUriOverride ||
|
|
2748
2819
|
((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.oauthRedirectUri) ||
|
|
2749
2820
|
(typeof location !== 'undefined' ? location.href : undefined);
|
|
2750
|
-
// CodeRabbit suggested to fail fast here, but the only situation where
|
|
2751
|
-
// redirectUri would be undefined is in non-browser environments, and
|
|
2752
|
-
// in those environments OAuth redirect does not make sense anyway
|
|
2753
|
-
// and will fail fast in startOAuthRedirect().
|
|
2754
|
-
// Start OAuth redirect flow - page navigates away
|
|
2755
2821
|
startOAuthRedirect({
|
|
2756
2822
|
databaseUrl: url,
|
|
2757
2823
|
provider,
|
|
2758
2824
|
redirectUri,
|
|
2759
2825
|
});
|
|
2760
2826
|
}
|
|
2827
|
+
/**
|
|
2828
|
+
* Converts a PolicyRejectionError to a DXCAlert for injection into prompts.
|
|
2829
|
+
*/
|
|
2830
|
+
function toPolicyAlert(err) {
|
|
2831
|
+
return {
|
|
2832
|
+
type: 'error',
|
|
2833
|
+
messageCode: err.code,
|
|
2834
|
+
message: err.message,
|
|
2835
|
+
messageParams: {},
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
/**
|
|
2839
|
+
* Tries to parse a failed Response as a structured PolicyError body.
|
|
2840
|
+
* Returns a DXCAlert if it is one, otherwise returns null.
|
|
2841
|
+
* Safe to call: reads body via clone() so the original Response is untouched.
|
|
2842
|
+
*/
|
|
2843
|
+
function tryParsePolicyAlert(res) {
|
|
2844
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2845
|
+
if (res.status !== 403)
|
|
2846
|
+
return null;
|
|
2847
|
+
try {
|
|
2848
|
+
const body = yield res.clone().json();
|
|
2849
|
+
if (isPolicyErrorBody(body)) {
|
|
2850
|
+
return toPolicyAlert(new PolicyRejectionError(body));
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
catch (_a) {
|
|
2854
|
+
// Not JSON
|
|
2855
|
+
}
|
|
2856
|
+
return null;
|
|
2857
|
+
});
|
|
2858
|
+
}
|
|
2761
2859
|
|
|
2762
2860
|
/** A way to log to console in production without terser stripping out
|
|
2763
2861
|
* it from the release bundle.
|
|
@@ -6038,6 +6136,7 @@ function createBlobResolvingCursor(cursor, table, blobSavingQueue, db) {
|
|
|
6038
6136
|
return cursor.start(() => {
|
|
6039
6137
|
const rawValue = cursor.value;
|
|
6040
6138
|
if (!rawValue || !hasUnresolvedBlobRefs(rawValue)) {
|
|
6139
|
+
wrappedCursor.value = rawValue;
|
|
6041
6140
|
onNext();
|
|
6042
6141
|
return;
|
|
6043
6142
|
}
|
|
@@ -6046,6 +6145,7 @@ function createBlobResolvingCursor(cursor, table, blobSavingQueue, db) {
|
|
|
6046
6145
|
onNext();
|
|
6047
6146
|
}, err => {
|
|
6048
6147
|
console.error('Failed to resolve BlobRefs for cursor value:', err);
|
|
6148
|
+
wrappedCursor.value = rawValue;
|
|
6049
6149
|
onNext();
|
|
6050
6150
|
});
|
|
6051
6151
|
});
|
|
@@ -8273,7 +8373,7 @@ function dexieCloud(dexie) {
|
|
|
8273
8373
|
const downloading$ = createDownloadingState();
|
|
8274
8374
|
dexie.cloud = {
|
|
8275
8375
|
// @ts-ignore
|
|
8276
|
-
version: "4.4.
|
|
8376
|
+
version: "4.4.4",
|
|
8277
8377
|
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
8278
8378
|
schema: null,
|
|
8279
8379
|
get currentUserId() {
|
|
@@ -8700,7 +8800,7 @@ function dexieCloud(dexie) {
|
|
|
8700
8800
|
}
|
|
8701
8801
|
}
|
|
8702
8802
|
// @ts-ignore
|
|
8703
|
-
dexieCloud.version = "4.4.
|
|
8803
|
+
dexieCloud.version = "4.4.4";
|
|
8704
8804
|
Dexie.Cloud = dexieCloud;
|
|
8705
8805
|
|
|
8706
8806
|
export { dexieCloud as default, defineYDocTrigger, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };
|