@prabhask5/stellar-engine 1.1.6 → 1.1.8
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 +68 -25
- package/dist/actions/remoteChange.d.ts +143 -18
- package/dist/actions/remoteChange.d.ts.map +1 -1
- package/dist/actions/remoteChange.js +182 -58
- package/dist/actions/remoteChange.js.map +1 -1
- package/dist/actions/truncateTooltip.d.ts +56 -0
- package/dist/actions/truncateTooltip.d.ts.map +1 -0
- package/dist/actions/truncateTooltip.js +312 -0
- package/dist/actions/truncateTooltip.js.map +1 -0
- package/dist/auth/admin.d.ts +40 -3
- package/dist/auth/admin.d.ts.map +1 -1
- package/dist/auth/admin.js +45 -5
- package/dist/auth/admin.js.map +1 -1
- package/dist/auth/crypto.d.ts +55 -5
- package/dist/auth/crypto.d.ts.map +1 -1
- package/dist/auth/crypto.js +58 -5
- package/dist/auth/crypto.js.map +1 -1
- package/dist/auth/deviceVerification.d.ts +236 -20
- package/dist/auth/deviceVerification.d.ts.map +1 -1
- package/dist/auth/deviceVerification.js +293 -40
- package/dist/auth/deviceVerification.js.map +1 -1
- package/dist/auth/displayUtils.d.ts +98 -0
- package/dist/auth/displayUtils.d.ts.map +1 -0
- package/dist/auth/displayUtils.js +133 -0
- package/dist/auth/displayUtils.js.map +1 -0
- package/dist/auth/loginGuard.d.ts +108 -14
- package/dist/auth/loginGuard.d.ts.map +1 -1
- package/dist/auth/loginGuard.js +153 -31
- package/dist/auth/loginGuard.js.map +1 -1
- package/dist/auth/offlineCredentials.d.ts +132 -15
- package/dist/auth/offlineCredentials.d.ts.map +1 -1
- package/dist/auth/offlineCredentials.js +167 -23
- package/dist/auth/offlineCredentials.js.map +1 -1
- package/dist/auth/offlineLogin.d.ts +96 -10
- package/dist/auth/offlineLogin.d.ts.map +1 -1
- package/dist/auth/offlineLogin.js +82 -15
- package/dist/auth/offlineLogin.js.map +1 -1
- package/dist/auth/offlineSession.d.ts +83 -9
- package/dist/auth/offlineSession.d.ts.map +1 -1
- package/dist/auth/offlineSession.js +104 -13
- package/dist/auth/offlineSession.js.map +1 -1
- package/dist/auth/resolveAuthState.d.ts +70 -8
- package/dist/auth/resolveAuthState.d.ts.map +1 -1
- package/dist/auth/resolveAuthState.js +142 -46
- package/dist/auth/resolveAuthState.js.map +1 -1
- package/dist/auth/singleUser.d.ts +390 -37
- package/dist/auth/singleUser.d.ts.map +1 -1
- package/dist/auth/singleUser.js +505 -133
- package/dist/auth/singleUser.js.map +1 -1
- package/dist/bin/install-pwa.d.ts +25 -0
- package/dist/bin/install-pwa.d.ts.map +1 -0
- package/dist/bin/install-pwa.js +2197 -0
- package/dist/bin/install-pwa.js.map +1 -0
- package/dist/config.d.ts +132 -12
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +87 -9
- package/dist/config.js.map +1 -1
- package/dist/conflicts.d.ts +246 -23
- package/dist/conflicts.d.ts.map +1 -1
- package/dist/conflicts.js +495 -46
- package/dist/conflicts.js.map +1 -1
- package/dist/data.d.ts +338 -18
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +385 -34
- package/dist/data.js.map +1 -1
- package/dist/database.d.ts +72 -14
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +120 -29
- package/dist/database.js.map +1 -1
- package/dist/debug.d.ts +77 -1
- package/dist/debug.d.ts.map +1 -1
- package/dist/debug.js +88 -1
- package/dist/debug.js.map +1 -1
- package/dist/deviceId.d.ts +38 -7
- package/dist/deviceId.d.ts.map +1 -1
- package/dist/deviceId.js +68 -10
- package/dist/deviceId.js.map +1 -1
- package/dist/engine.d.ts +175 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +831 -110
- package/dist/engine.js.map +1 -1
- package/dist/entries/actions.d.ts +14 -0
- package/dist/entries/actions.d.ts.map +1 -1
- package/dist/entries/actions.js +27 -1
- package/dist/entries/actions.js.map +1 -1
- package/dist/entries/auth.d.ts +16 -0
- package/dist/entries/auth.d.ts.map +1 -1
- package/dist/entries/auth.js +73 -1
- package/dist/entries/auth.js.map +1 -1
- package/dist/entries/config.d.ts +12 -0
- package/dist/entries/config.d.ts.map +1 -1
- package/dist/entries/config.js +18 -1
- package/dist/entries/config.js.map +1 -1
- package/dist/entries/kit.d.ts +21 -9
- package/dist/entries/kit.d.ts.map +1 -1
- package/dist/entries/kit.js +57 -8
- package/dist/entries/kit.js.map +1 -1
- package/dist/entries/stores.d.ts +11 -0
- package/dist/entries/stores.d.ts.map +1 -1
- package/dist/entries/stores.js +43 -2
- package/dist/entries/stores.js.map +1 -1
- package/dist/entries/types.d.ts +11 -1
- package/dist/entries/types.d.ts.map +1 -1
- package/dist/entries/types.js +10 -0
- package/dist/entries/types.js.map +1 -1
- package/dist/entries/utils.d.ts +7 -1
- package/dist/entries/utils.d.ts.map +1 -1
- package/dist/entries/utils.js +23 -2
- package/dist/entries/utils.js.map +1 -1
- package/dist/entries/vite.d.ts +20 -0
- package/dist/entries/vite.d.ts.map +1 -0
- package/dist/entries/vite.js +26 -0
- package/dist/entries/vite.js.map +1 -0
- package/dist/index.d.ts +33 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +176 -21
- package/dist/index.js.map +1 -1
- package/dist/kit/auth.d.ts +80 -0
- package/dist/kit/auth.d.ts.map +1 -0
- package/dist/kit/auth.js +72 -0
- package/dist/kit/auth.js.map +1 -0
- package/dist/kit/confirm.d.ts +111 -0
- package/dist/kit/confirm.d.ts.map +1 -0
- package/dist/kit/confirm.js +169 -0
- package/dist/kit/confirm.js.map +1 -0
- package/dist/kit/loads.d.ts +189 -0
- package/dist/kit/loads.d.ts.map +1 -0
- package/dist/kit/loads.js +205 -0
- package/dist/kit/loads.js.map +1 -0
- package/dist/kit/server.d.ts +175 -0
- package/dist/kit/server.d.ts.map +1 -0
- package/dist/kit/server.js +297 -0
- package/dist/kit/server.js.map +1 -0
- package/dist/kit/sw.d.ts +176 -0
- package/dist/kit/sw.d.ts.map +1 -0
- package/dist/kit/sw.js +320 -0
- package/dist/kit/sw.js.map +1 -0
- package/dist/queue.d.ts +274 -0
- package/dist/queue.d.ts.map +1 -1
- package/dist/queue.js +556 -38
- package/dist/queue.js.map +1 -1
- package/dist/realtime.d.ts +241 -27
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +633 -109
- package/dist/realtime.js.map +1 -1
- package/dist/runtime/runtimeConfig.d.ts +91 -16
- package/dist/runtime/runtimeConfig.d.ts.map +1 -1
- package/dist/runtime/runtimeConfig.js +146 -19
- package/dist/runtime/runtimeConfig.js.map +1 -1
- package/dist/stores/authState.d.ts +150 -11
- package/dist/stores/authState.d.ts.map +1 -1
- package/dist/stores/authState.js +169 -17
- package/dist/stores/authState.js.map +1 -1
- package/dist/stores/network.d.ts +39 -0
- package/dist/stores/network.d.ts.map +1 -1
- package/dist/stores/network.js +169 -16
- package/dist/stores/network.js.map +1 -1
- package/dist/stores/remoteChanges.d.ts +327 -52
- package/dist/stores/remoteChanges.d.ts.map +1 -1
- package/dist/stores/remoteChanges.js +337 -75
- package/dist/stores/remoteChanges.js.map +1 -1
- package/dist/stores/sync.d.ts +130 -0
- package/dist/stores/sync.d.ts.map +1 -1
- package/dist/stores/sync.js +167 -7
- package/dist/stores/sync.js.map +1 -1
- package/dist/supabase/auth.d.ts +326 -19
- package/dist/supabase/auth.d.ts.map +1 -1
- package/dist/supabase/auth.js +374 -26
- package/dist/supabase/auth.js.map +1 -1
- package/dist/supabase/client.d.ts +79 -6
- package/dist/supabase/client.d.ts.map +1 -1
- package/dist/supabase/client.js +158 -15
- package/dist/supabase/client.js.map +1 -1
- package/dist/supabase/validate.d.ts +101 -7
- package/dist/supabase/validate.d.ts.map +1 -1
- package/dist/supabase/validate.js +117 -8
- package/dist/supabase/validate.js.map +1 -1
- package/dist/sw/build/vite-plugin.d.ts +74 -0
- package/dist/sw/build/vite-plugin.d.ts.map +1 -0
- package/dist/sw/build/vite-plugin.js +183 -0
- package/dist/sw/build/vite-plugin.js.map +1 -0
- package/dist/sw/sw.js +669 -0
- package/dist/types.d.ts +150 -45
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -10
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +55 -13
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +83 -22
- package/dist/utils.js.map +1 -1
- package/package.json +20 -22
- package/src/components/DeferredChangesBanner.svelte +477 -0
- package/src/components/SyncStatus.svelte +1732 -0
- package/dist/crdt/awareness.d.ts +0 -54
- package/dist/crdt/awareness.d.ts.map +0 -1
- package/dist/crdt/awareness.js +0 -219
- package/dist/crdt/awareness.js.map +0 -1
- package/dist/crdt/doc.d.ts +0 -56
- package/dist/crdt/doc.d.ts.map +0 -1
- package/dist/crdt/doc.js +0 -130
- package/dist/crdt/doc.js.map +0 -1
- package/dist/crdt/index.d.ts +0 -15
- package/dist/crdt/index.d.ts.map +0 -1
- package/dist/crdt/index.js +0 -20
- package/dist/crdt/index.js.map +0 -1
- package/dist/crdt/offline.d.ts +0 -91
- package/dist/crdt/offline.d.ts.map +0 -1
- package/dist/crdt/offline.js +0 -353
- package/dist/crdt/offline.js.map +0 -1
- package/dist/crdt/sync.d.ts +0 -58
- package/dist/crdt/sync.d.ts.map +0 -1
- package/dist/crdt/sync.js +0 -399
- package/dist/crdt/sync.js.map +0 -1
- package/dist/crdt/types.d.ts +0 -62
- package/dist/crdt/types.d.ts.map +0 -1
- package/dist/crdt/types.js +0 -7
- package/dist/crdt/types.js.map +0 -1
- package/dist/email/sendEmail.d.ts +0 -31
- package/dist/email/sendEmail.d.ts.map +0 -1
- package/dist/email/sendEmail.js +0 -39
- package/dist/email/sendEmail.js.map +0 -1
- package/dist/email/validateSmtp.d.ts +0 -18
- package/dist/email/validateSmtp.d.ts.map +0 -1
- package/dist/email/validateSmtp.js +0 -33
- package/dist/email/validateSmtp.js.map +0 -1
- package/dist/entries/crdt.d.ts +0 -3
- package/dist/entries/crdt.d.ts.map +0 -1
- package/dist/entries/crdt.js +0 -13
- package/dist/entries/crdt.js.map +0 -1
- package/dist/entries/email.d.ts +0 -4
- package/dist/entries/email.d.ts.map +0 -1
- package/dist/entries/email.js +0 -4
- package/dist/entries/email.js.map +0 -1
- package/dist/kit/authPresets.d.ts +0 -28
- package/dist/kit/authPresets.d.ts.map +0 -1
- package/dist/kit/authPresets.js +0 -23
- package/dist/kit/authPresets.js.map +0 -1
- package/dist/kit/configEndpoint.d.ts +0 -18
- package/dist/kit/configEndpoint.d.ts.map +0 -1
- package/dist/kit/configEndpoint.js +0 -27
- package/dist/kit/configEndpoint.js.map +0 -1
- package/dist/kit/deployEndpoint.d.ts +0 -22
- package/dist/kit/deployEndpoint.d.ts.map +0 -1
- package/dist/kit/deployEndpoint.js +0 -79
- package/dist/kit/deployEndpoint.js.map +0 -1
- package/dist/kit/layoutLoad.d.ts +0 -23
- package/dist/kit/layoutLoad.d.ts.map +0 -1
- package/dist/kit/layoutLoad.js +0 -41
- package/dist/kit/layoutLoad.js.map +0 -1
- package/dist/kit/protectedLoad.d.ts +0 -16
- package/dist/kit/protectedLoad.d.ts.map +0 -1
- package/dist/kit/protectedLoad.js +0 -28
- package/dist/kit/protectedLoad.js.map +0 -1
- package/dist/kit/setupLoad.d.ts +0 -11
- package/dist/kit/setupLoad.d.ts.map +0 -1
- package/dist/kit/setupLoad.js +0 -28
- package/dist/kit/setupLoad.js.map +0 -1
- package/dist/kit/validateEndpoint.d.ts +0 -9
- package/dist/kit/validateEndpoint.d.ts.map +0 -1
- package/dist/kit/validateEndpoint.js +0 -25
- package/dist/kit/validateEndpoint.js.map +0 -1
- package/dist/kit/vercelApi.d.ts +0 -6
- package/dist/kit/vercelApi.d.ts.map +0 -1
- package/dist/kit/vercelApi.js +0 -48
- package/dist/kit/vercelApi.js.map +0 -1
|
@@ -1,41 +1,158 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Offline Credentials Management
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Offline Credentials Management
|
|
3
|
+
*
|
|
4
|
+
* Handles caching, retrieval, verification, and update of user credentials
|
|
5
|
+
* in IndexedDB for offline login support. When the user successfully
|
|
6
|
+
* authenticates online via Supabase, their credentials are cached locally
|
|
7
|
+
* (with the password SHA-256-hashed) so they can log in again while offline.
|
|
8
|
+
*
|
|
9
|
+
* Architecture:
|
|
10
|
+
* - Credentials are stored as a singleton record (key: `'current_user'`) in the
|
|
11
|
+
* `offlineCredentials` IndexedDB table.
|
|
12
|
+
* - Only one set of credentials is cached at a time. In multi-user apps, the
|
|
13
|
+
* last successfully authenticated user's credentials are stored.
|
|
14
|
+
* - The profile blob is extracted via the host app's `profileExtractor` config
|
|
15
|
+
* callback, or falls back to raw Supabase `user_metadata`.
|
|
16
|
+
*
|
|
17
|
+
* Security considerations:
|
|
18
|
+
* - Passwords are **always** hashed with SHA-256 before storage. The plaintext
|
|
19
|
+
* password is never persisted.
|
|
20
|
+
* - Legacy plaintext credentials (from before the hashing migration) are
|
|
21
|
+
* supported in `verifyOfflineCredentials` via the `isAlreadyHashed` check,
|
|
22
|
+
* but new writes always hash.
|
|
23
|
+
* - A paranoid read-back verification is performed after `cacheOfflineCredentials`
|
|
24
|
+
* to ensure the password was actually persisted (guards against silent
|
|
25
|
+
* IndexedDB write failures).
|
|
26
|
+
* - Credentials are cleared on logout via `clearOfflineCredentials`.
|
|
27
|
+
*
|
|
28
|
+
* @module auth/offlineCredentials
|
|
4
29
|
*/
|
|
5
30
|
import type { OfflineCredentials } from '../types';
|
|
6
31
|
import type { User, Session } from '@supabase/supabase-js';
|
|
7
32
|
/**
|
|
8
|
-
* Cache user credentials for offline login
|
|
9
|
-
*
|
|
33
|
+
* Cache user credentials for offline login.
|
|
34
|
+
*
|
|
35
|
+
* Called after a successful Supabase login to persist a hashed copy of the
|
|
36
|
+
* user's credentials in IndexedDB. Subsequent offline logins will verify
|
|
37
|
+
* against these cached credentials.
|
|
38
|
+
*
|
|
39
|
+
* @param email - The user's email address (used for offline identity matching).
|
|
40
|
+
* @param password - The user's plaintext password. Will be SHA-256-hashed before storage.
|
|
41
|
+
* @param user - The Supabase `User` object, used to extract `userId` and profile data.
|
|
42
|
+
* @param _session - The Supabase `Session` object. Currently unused but accepted for
|
|
43
|
+
* API symmetry with the online auth flow (reserved for future use).
|
|
44
|
+
*
|
|
45
|
+
* @throws {Error} If `email` or `password` is empty (prevents storing incomplete credentials).
|
|
46
|
+
* @throws {Error} If the write-back verification fails (password not persisted in IndexedDB).
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const { data } = await supabase.auth.signInWithPassword({ email, password });
|
|
51
|
+
* if (data.user && data.session) {
|
|
52
|
+
* await cacheOfflineCredentials(email, password, data.user, data.session);
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @see {@link getOfflineCredentials} to retrieve the cached credentials.
|
|
57
|
+
* @see {@link clearOfflineCredentials} to remove them on logout.
|
|
10
58
|
*/
|
|
11
59
|
export declare function cacheOfflineCredentials(email: string, password: string, user: User, _session: Session): Promise<void>;
|
|
12
60
|
/**
|
|
13
|
-
* Get cached offline credentials
|
|
14
|
-
*
|
|
61
|
+
* Get cached offline credentials from IndexedDB.
|
|
62
|
+
*
|
|
63
|
+
* Returns the singleton `OfflineCredentials` record, or `null` if no
|
|
64
|
+
* credentials have been cached (e.g., user has never logged in online
|
|
65
|
+
* on this device).
|
|
66
|
+
*
|
|
67
|
+
* @returns The cached credentials, or `null` if none exist.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const creds = await getOfflineCredentials();
|
|
72
|
+
* if (creds) {
|
|
73
|
+
* console.log('Cached user:', creds.email);
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
15
76
|
*/
|
|
16
77
|
export declare function getOfflineCredentials(): Promise<OfflineCredentials | null>;
|
|
17
78
|
/**
|
|
18
|
-
* Verify email and password against cached credentials
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
79
|
+
* Verify email and password against cached credentials.
|
|
80
|
+
*
|
|
81
|
+
* Performs a multi-field comparison:
|
|
82
|
+
* 1. Checks that cached credentials exist.
|
|
83
|
+
* 2. Verifies the `userId` matches (prevents cross-user attacks).
|
|
84
|
+
* 3. Verifies the `email` matches (prevents credential reuse across accounts).
|
|
85
|
+
* 4. Verifies the password by hashing the input and comparing against the
|
|
86
|
+
* stored hash (or plaintext for legacy records).
|
|
87
|
+
*
|
|
88
|
+
* @param email - The email to verify against cached credentials.
|
|
89
|
+
* @param password - The plaintext password to verify.
|
|
90
|
+
* @param expectedUserId - The Supabase user ID that the credentials should belong to.
|
|
91
|
+
* Prevents offline login with credentials cached from a
|
|
92
|
+
* different user.
|
|
93
|
+
* @returns An object with `valid: true` on success, or `valid: false` with a
|
|
94
|
+
* `reason` string identifying which check failed.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const result = await verifyOfflineCredentials(email, password, userId);
|
|
99
|
+
* if (result.valid) {
|
|
100
|
+
* await createOfflineSession(userId);
|
|
101
|
+
* } else {
|
|
102
|
+
* console.warn('Verification failed:', result.reason);
|
|
103
|
+
* }
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* @see {@link cacheOfflineCredentials} for how credentials are stored.
|
|
23
107
|
*/
|
|
24
108
|
export declare function verifyOfflineCredentials(email: string, password: string, expectedUserId: string): Promise<{
|
|
25
109
|
valid: boolean;
|
|
26
110
|
reason?: string;
|
|
27
111
|
}>;
|
|
28
112
|
/**
|
|
29
|
-
* Update the cached password
|
|
30
|
-
*
|
|
113
|
+
* Update the cached password hash after an online password change.
|
|
114
|
+
*
|
|
115
|
+
* Should be called whenever the user changes their password while online,
|
|
116
|
+
* so the offline cache stays in sync and the login guard does not flag
|
|
117
|
+
* the old hash as stale.
|
|
118
|
+
*
|
|
119
|
+
* @param newPassword - The new plaintext password. Will be SHA-256-hashed before storage.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* await supabase.auth.updateUser({ password: newPassword });
|
|
124
|
+
* await updateOfflineCredentialsPassword(newPassword);
|
|
125
|
+
* ```
|
|
31
126
|
*/
|
|
32
127
|
export declare function updateOfflineCredentialsPassword(newPassword: string): Promise<void>;
|
|
33
128
|
/**
|
|
34
|
-
* Update user profile in cached credentials
|
|
129
|
+
* Update the user profile in cached credentials after an online profile update.
|
|
130
|
+
*
|
|
131
|
+
* Replaces the entire `profile` blob with the provided object and updates
|
|
132
|
+
* the `cachedAt` timestamp.
|
|
133
|
+
*
|
|
134
|
+
* @param profile - The new profile data to cache (e.g., `{ firstName, lastName, avatar }`).
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* await supabase.auth.updateUser({ data: { firstName: 'Jane' } });
|
|
139
|
+
* await updateOfflineCredentialsProfile({ firstName: 'Jane', lastName: 'Doe' });
|
|
140
|
+
* ```
|
|
35
141
|
*/
|
|
36
142
|
export declare function updateOfflineCredentialsProfile(profile: Record<string, unknown>): Promise<void>;
|
|
37
143
|
/**
|
|
38
|
-
* Clear all cached offline credentials
|
|
144
|
+
* Clear all cached offline credentials from IndexedDB.
|
|
145
|
+
*
|
|
146
|
+
* Must be called on logout to ensure no stale credentials remain on the
|
|
147
|
+
* device that could be used for unauthorized offline access.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* await supabase.auth.signOut();
|
|
152
|
+
* await clearOfflineCredentials();
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
* @see {@link cacheOfflineCredentials} for storing credentials on login.
|
|
39
156
|
*/
|
|
40
157
|
export declare function clearOfflineCredentials(): Promise<void>;
|
|
41
158
|
//# sourceMappingURL=offlineCredentials.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"offlineCredentials.d.ts","sourceRoot":"","sources":["../../src/auth/offlineCredentials.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"offlineCredentials.d.ts","sourceRoot":"","sources":["../../src/auth/offlineCredentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAmB3D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,IAAI,CAAC,CAyCf;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAQhF;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkD9C;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gCAAgC,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYzF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAG7D"}
|
|
@@ -1,24 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Offline Credentials Management
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Offline Credentials Management
|
|
3
|
+
*
|
|
4
|
+
* Handles caching, retrieval, verification, and update of user credentials
|
|
5
|
+
* in IndexedDB for offline login support. When the user successfully
|
|
6
|
+
* authenticates online via Supabase, their credentials are cached locally
|
|
7
|
+
* (with the password SHA-256-hashed) so they can log in again while offline.
|
|
8
|
+
*
|
|
9
|
+
* Architecture:
|
|
10
|
+
* - Credentials are stored as a singleton record (key: `'current_user'`) in the
|
|
11
|
+
* `offlineCredentials` IndexedDB table.
|
|
12
|
+
* - Only one set of credentials is cached at a time. In multi-user apps, the
|
|
13
|
+
* last successfully authenticated user's credentials are stored.
|
|
14
|
+
* - The profile blob is extracted via the host app's `profileExtractor` config
|
|
15
|
+
* callback, or falls back to raw Supabase `user_metadata`.
|
|
16
|
+
*
|
|
17
|
+
* Security considerations:
|
|
18
|
+
* - Passwords are **always** hashed with SHA-256 before storage. The plaintext
|
|
19
|
+
* password is never persisted.
|
|
20
|
+
* - Legacy plaintext credentials (from before the hashing migration) are
|
|
21
|
+
* supported in `verifyOfflineCredentials` via the `isAlreadyHashed` check,
|
|
22
|
+
* but new writes always hash.
|
|
23
|
+
* - A paranoid read-back verification is performed after `cacheOfflineCredentials`
|
|
24
|
+
* to ensure the password was actually persisted (guards against silent
|
|
25
|
+
* IndexedDB write failures).
|
|
26
|
+
* - Credentials are cleared on logout via `clearOfflineCredentials`.
|
|
27
|
+
*
|
|
28
|
+
* @module auth/offlineCredentials
|
|
4
29
|
*/
|
|
5
30
|
import { getEngineConfig } from '../config';
|
|
6
31
|
import { debugWarn, debugError } from '../debug';
|
|
7
32
|
import { hashValue, isAlreadyHashed } from './crypto';
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// CONSTANTS
|
|
35
|
+
// =============================================================================
|
|
36
|
+
/**
|
|
37
|
+
* Singleton key used to store and retrieve the cached credentials record
|
|
38
|
+
* in the `offlineCredentials` IndexedDB table. Only one credential set is
|
|
39
|
+
* cached at any time.
|
|
40
|
+
*/
|
|
8
41
|
const CREDENTIALS_ID = 'current_user';
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// PUBLIC API -- Cache & Retrieve
|
|
44
|
+
// =============================================================================
|
|
9
45
|
/**
|
|
10
|
-
* Cache user credentials for offline login
|
|
11
|
-
*
|
|
46
|
+
* Cache user credentials for offline login.
|
|
47
|
+
*
|
|
48
|
+
* Called after a successful Supabase login to persist a hashed copy of the
|
|
49
|
+
* user's credentials in IndexedDB. Subsequent offline logins will verify
|
|
50
|
+
* against these cached credentials.
|
|
51
|
+
*
|
|
52
|
+
* @param email - The user's email address (used for offline identity matching).
|
|
53
|
+
* @param password - The user's plaintext password. Will be SHA-256-hashed before storage.
|
|
54
|
+
* @param user - The Supabase `User` object, used to extract `userId` and profile data.
|
|
55
|
+
* @param _session - The Supabase `Session` object. Currently unused but accepted for
|
|
56
|
+
* API symmetry with the online auth flow (reserved for future use).
|
|
57
|
+
*
|
|
58
|
+
* @throws {Error} If `email` or `password` is empty (prevents storing incomplete credentials).
|
|
59
|
+
* @throws {Error} If the write-back verification fails (password not persisted in IndexedDB).
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const { data } = await supabase.auth.signInWithPassword({ email, password });
|
|
64
|
+
* if (data.user && data.session) {
|
|
65
|
+
* await cacheOfflineCredentials(email, password, data.user, data.session);
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @see {@link getOfflineCredentials} to retrieve the cached credentials.
|
|
70
|
+
* @see {@link clearOfflineCredentials} to remove them on logout.
|
|
12
71
|
*/
|
|
13
72
|
export async function cacheOfflineCredentials(email, password, user, _session) {
|
|
14
|
-
|
|
73
|
+
/* Validate inputs to prevent storing incomplete credentials that would
|
|
74
|
+
cause confusing verification failures later. */
|
|
15
75
|
if (!email || !password) {
|
|
16
76
|
debugError('[Auth] Cannot cache credentials: email or password is empty');
|
|
17
77
|
throw new Error('Cannot cache credentials: email or password is empty');
|
|
18
78
|
}
|
|
19
79
|
const config = getEngineConfig();
|
|
20
80
|
const db = config.db;
|
|
21
|
-
|
|
81
|
+
/* Extract a normalized profile using the host app's profileExtractor,
|
|
82
|
+
or fall back to raw Supabase user_metadata. This allows the host app
|
|
83
|
+
to control which fields are available offline (e.g., firstName, role). */
|
|
22
84
|
const profile = config.auth?.profileExtractor
|
|
23
85
|
? config.auth.profileExtractor(user.user_metadata || {})
|
|
24
86
|
: user.user_metadata || {};
|
|
@@ -31,9 +93,12 @@ export async function cacheOfflineCredentials(email, password, user, _session) {
|
|
|
31
93
|
profile,
|
|
32
94
|
cachedAt: new Date().toISOString()
|
|
33
95
|
};
|
|
34
|
-
|
|
96
|
+
/* Use put (upsert) to insert or update the singleton record. */
|
|
35
97
|
await db.table('offlineCredentials').put(credentials);
|
|
36
|
-
|
|
98
|
+
/* Paranoid read-back: verify the credentials were stored correctly.
|
|
99
|
+
IndexedDB writes can silently fail in quota-exceeded or private-browsing
|
|
100
|
+
scenarios; catching this early gives a clear error instead of a mysterious
|
|
101
|
+
"wrong password" on the next offline login. */
|
|
37
102
|
const stored = await db.table('offlineCredentials').get(CREDENTIALS_ID);
|
|
38
103
|
if (!stored || !stored.password) {
|
|
39
104
|
debugError('[Auth] Credentials were not stored correctly - password missing');
|
|
@@ -41,8 +106,21 @@ export async function cacheOfflineCredentials(email, password, user, _session) {
|
|
|
41
106
|
}
|
|
42
107
|
}
|
|
43
108
|
/**
|
|
44
|
-
* Get cached offline credentials
|
|
45
|
-
*
|
|
109
|
+
* Get cached offline credentials from IndexedDB.
|
|
110
|
+
*
|
|
111
|
+
* Returns the singleton `OfflineCredentials` record, or `null` if no
|
|
112
|
+
* credentials have been cached (e.g., user has never logged in online
|
|
113
|
+
* on this device).
|
|
114
|
+
*
|
|
115
|
+
* @returns The cached credentials, or `null` if none exist.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const creds = await getOfflineCredentials();
|
|
120
|
+
* if (creds) {
|
|
121
|
+
* console.log('Cached user:', creds.email);
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
46
124
|
*/
|
|
47
125
|
export async function getOfflineCredentials() {
|
|
48
126
|
const db = getEngineConfig().db;
|
|
@@ -52,12 +130,38 @@ export async function getOfflineCredentials() {
|
|
|
52
130
|
}
|
|
53
131
|
return credentials;
|
|
54
132
|
}
|
|
133
|
+
// =============================================================================
|
|
134
|
+
// PUBLIC API -- Verification
|
|
135
|
+
// =============================================================================
|
|
55
136
|
/**
|
|
56
|
-
* Verify email and password against cached credentials
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
137
|
+
* Verify email and password against cached credentials.
|
|
138
|
+
*
|
|
139
|
+
* Performs a multi-field comparison:
|
|
140
|
+
* 1. Checks that cached credentials exist.
|
|
141
|
+
* 2. Verifies the `userId` matches (prevents cross-user attacks).
|
|
142
|
+
* 3. Verifies the `email` matches (prevents credential reuse across accounts).
|
|
143
|
+
* 4. Verifies the password by hashing the input and comparing against the
|
|
144
|
+
* stored hash (or plaintext for legacy records).
|
|
145
|
+
*
|
|
146
|
+
* @param email - The email to verify against cached credentials.
|
|
147
|
+
* @param password - The plaintext password to verify.
|
|
148
|
+
* @param expectedUserId - The Supabase user ID that the credentials should belong to.
|
|
149
|
+
* Prevents offline login with credentials cached from a
|
|
150
|
+
* different user.
|
|
151
|
+
* @returns An object with `valid: true` on success, or `valid: false` with a
|
|
152
|
+
* `reason` string identifying which check failed.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* const result = await verifyOfflineCredentials(email, password, userId);
|
|
157
|
+
* if (result.valid) {
|
|
158
|
+
* await createOfflineSession(userId);
|
|
159
|
+
* } else {
|
|
160
|
+
* console.warn('Verification failed:', result.reason);
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @see {@link cacheOfflineCredentials} for how credentials are stored.
|
|
61
165
|
*/
|
|
62
166
|
export async function verifyOfflineCredentials(email, password, expectedUserId) {
|
|
63
167
|
const credentials = await getOfflineCredentials();
|
|
@@ -65,7 +169,9 @@ export async function verifyOfflineCredentials(email, password, expectedUserId)
|
|
|
65
169
|
debugWarn('[Auth] No credentials found in database');
|
|
66
170
|
return { valid: false, reason: 'no_credentials' };
|
|
67
171
|
}
|
|
68
|
-
|
|
172
|
+
/* SECURITY: Verify all identity fields match. Checking userId AND email
|
|
173
|
+
provides defense-in-depth -- even if one field is spoofed, the other
|
|
174
|
+
must also match. */
|
|
69
175
|
if (credentials.userId !== expectedUserId) {
|
|
70
176
|
debugWarn('[Auth] Credential userId mismatch:', credentials.userId, '!==', expectedUserId);
|
|
71
177
|
return { valid: false, reason: 'user_mismatch' };
|
|
@@ -78,15 +184,17 @@ export async function verifyOfflineCredentials(email, password, expectedUserId)
|
|
|
78
184
|
debugWarn('[Auth] No password stored in credentials');
|
|
79
185
|
return { valid: false, reason: 'no_stored_password' };
|
|
80
186
|
}
|
|
81
|
-
|
|
82
|
-
|
|
187
|
+
/* Compare passwords: if the stored password is a 64-char hex string, it is
|
|
188
|
+
a SHA-256 hash -- hash the input and compare. Otherwise, treat it as a
|
|
189
|
+
legacy plaintext value for backward compatibility. */
|
|
83
190
|
let passwordMatch;
|
|
84
191
|
if (isAlreadyHashed(credentials.password)) {
|
|
85
192
|
const hashedInput = await hashValue(password);
|
|
86
193
|
passwordMatch = credentials.password === hashedInput;
|
|
87
194
|
}
|
|
88
195
|
else {
|
|
89
|
-
|
|
196
|
+
/* Legacy plaintext comparison -- only applies to credentials cached before
|
|
197
|
+
the SHA-256 migration. New caches always store hashed passwords. */
|
|
90
198
|
passwordMatch = credentials.password === password;
|
|
91
199
|
}
|
|
92
200
|
if (!passwordMatch) {
|
|
@@ -95,9 +203,23 @@ export async function verifyOfflineCredentials(email, password, expectedUserId)
|
|
|
95
203
|
}
|
|
96
204
|
return { valid: true };
|
|
97
205
|
}
|
|
206
|
+
// =============================================================================
|
|
207
|
+
// PUBLIC API -- Update & Clear
|
|
208
|
+
// =============================================================================
|
|
98
209
|
/**
|
|
99
|
-
* Update the cached password
|
|
100
|
-
*
|
|
210
|
+
* Update the cached password hash after an online password change.
|
|
211
|
+
*
|
|
212
|
+
* Should be called whenever the user changes their password while online,
|
|
213
|
+
* so the offline cache stays in sync and the login guard does not flag
|
|
214
|
+
* the old hash as stale.
|
|
215
|
+
*
|
|
216
|
+
* @param newPassword - The new plaintext password. Will be SHA-256-hashed before storage.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```ts
|
|
220
|
+
* await supabase.auth.updateUser({ password: newPassword });
|
|
221
|
+
* await updateOfflineCredentialsPassword(newPassword);
|
|
222
|
+
* ```
|
|
101
223
|
*/
|
|
102
224
|
export async function updateOfflineCredentialsPassword(newPassword) {
|
|
103
225
|
const credentials = await getOfflineCredentials();
|
|
@@ -112,7 +234,18 @@ export async function updateOfflineCredentialsPassword(newPassword) {
|
|
|
112
234
|
});
|
|
113
235
|
}
|
|
114
236
|
/**
|
|
115
|
-
* Update user profile in cached credentials
|
|
237
|
+
* Update the user profile in cached credentials after an online profile update.
|
|
238
|
+
*
|
|
239
|
+
* Replaces the entire `profile` blob with the provided object and updates
|
|
240
|
+
* the `cachedAt` timestamp.
|
|
241
|
+
*
|
|
242
|
+
* @param profile - The new profile data to cache (e.g., `{ firstName, lastName, avatar }`).
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* await supabase.auth.updateUser({ data: { firstName: 'Jane' } });
|
|
247
|
+
* await updateOfflineCredentialsProfile({ firstName: 'Jane', lastName: 'Doe' });
|
|
248
|
+
* ```
|
|
116
249
|
*/
|
|
117
250
|
export async function updateOfflineCredentialsProfile(profile) {
|
|
118
251
|
const credentials = await getOfflineCredentials();
|
|
@@ -126,7 +259,18 @@ export async function updateOfflineCredentialsProfile(profile) {
|
|
|
126
259
|
});
|
|
127
260
|
}
|
|
128
261
|
/**
|
|
129
|
-
* Clear all cached offline credentials
|
|
262
|
+
* Clear all cached offline credentials from IndexedDB.
|
|
263
|
+
*
|
|
264
|
+
* Must be called on logout to ensure no stale credentials remain on the
|
|
265
|
+
* device that could be used for unauthorized offline access.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* await supabase.auth.signOut();
|
|
270
|
+
* await clearOfflineCredentials();
|
|
271
|
+
* ```
|
|
272
|
+
*
|
|
273
|
+
* @see {@link cacheOfflineCredentials} for storing credentials on login.
|
|
130
274
|
*/
|
|
131
275
|
export async function clearOfflineCredentials() {
|
|
132
276
|
const db = getEngineConfig().db;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"offlineCredentials.js","sourceRoot":"","sources":["../../src/auth/offlineCredentials.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"offlineCredentials.js","sourceRoot":"","sources":["../../src/auth/offlineCredentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,33 +1,119 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Offline Login
|
|
2
|
+
* @fileoverview Offline Login Orchestrator
|
|
3
3
|
*
|
|
4
|
-
* Provides high-level offline sign-in and credential info functions,
|
|
5
|
-
*
|
|
4
|
+
* Provides high-level offline sign-in and credential info functions, serving as
|
|
5
|
+
* the primary entry point for offline authentication. This module absorbs the
|
|
6
|
+
* login page's direct use of offline auth internals, offering a clean API that
|
|
7
|
+
* coordinates credential retrieval, verification, and session creation.
|
|
8
|
+
*
|
|
9
|
+
* Architecture:
|
|
10
|
+
* - `signInOffline` is the main flow: fetch cached credentials, verify them,
|
|
11
|
+
* create an offline session, and validate it was persisted.
|
|
12
|
+
* - `getOfflineLoginInfo` is a read-only helper that exposes non-sensitive
|
|
13
|
+
* display data (email, name) for pre-populating the offline login UI.
|
|
14
|
+
* - Both functions are designed to never throw -- they return structured result
|
|
15
|
+
* objects with typed error reasons for the caller to handle.
|
|
16
|
+
*
|
|
17
|
+
* Security considerations:
|
|
18
|
+
* - Offline login is inherently less secure than online Supabase authentication.
|
|
19
|
+
* It relies on SHA-256-hashed credentials stored in IndexedDB, which a
|
|
20
|
+
* sufficiently motivated attacker with device access could extract.
|
|
21
|
+
* - The offline session token (`offlineToken`) is a random UUID that serves as
|
|
22
|
+
* a local-only proof of authentication. It is NOT a JWT and carries no claims.
|
|
23
|
+
* - `getOfflineLoginInfo` intentionally excludes the password hash and userId
|
|
24
|
+
* from its return value to minimize exposure of sensitive data.
|
|
25
|
+
*
|
|
26
|
+
* @module auth/offlineLogin
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Structured result returned by {@link signInOffline}.
|
|
30
|
+
*
|
|
31
|
+
* On success: `{ success: true }`.
|
|
32
|
+
* On failure: `{ success: false, reason }` where `reason` identifies which
|
|
33
|
+
* step of the offline login flow failed.
|
|
6
34
|
*/
|
|
7
35
|
interface OfflineLoginResult {
|
|
36
|
+
/** Whether the offline login succeeded. */
|
|
8
37
|
success: boolean;
|
|
38
|
+
/** Human-readable error message (currently unused; `reason` is preferred). */
|
|
9
39
|
error?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Machine-readable failure reason, used by the login UI to display
|
|
42
|
+
* context-appropriate error messages.
|
|
43
|
+
*
|
|
44
|
+
* - `'no_credentials'` -- No cached credentials exist on this device.
|
|
45
|
+
* - `'no_stored_password'` -- Credentials exist but the password hash is missing.
|
|
46
|
+
* - `'user_mismatch'` -- The cached userId does not match the expected user.
|
|
47
|
+
* - `'email_mismatch'` -- The entered email does not match the cached email.
|
|
48
|
+
* - `'password_mismatch'` -- The entered password does not match the cached hash.
|
|
49
|
+
* - `'session_failed'` -- Credential verification passed but session creation
|
|
50
|
+
* or persistence failed.
|
|
51
|
+
*/
|
|
10
52
|
reason?: 'no_credentials' | 'no_stored_password' | 'user_mismatch' | 'email_mismatch' | 'password_mismatch' | 'session_failed';
|
|
11
53
|
}
|
|
12
54
|
/**
|
|
13
55
|
* Sign in offline using cached credentials.
|
|
14
56
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
57
|
+
* Executes the full offline authentication flow:
|
|
58
|
+
* 1. Fetches cached offline credentials from IndexedDB.
|
|
59
|
+
* 2. Verifies the provided email + password against cached credentials.
|
|
60
|
+
* 3. Creates an offline session (random UUID token) in IndexedDB.
|
|
61
|
+
* 4. Validates the session was actually persisted (read-back check).
|
|
62
|
+
* 5. Returns a structured result with a typed error reason on failure.
|
|
63
|
+
*
|
|
64
|
+
* @param email - The email address entered by the user.
|
|
65
|
+
* @param password - The plaintext password entered by the user.
|
|
66
|
+
* @returns A promise resolving to an {@link OfflineLoginResult}.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const result = await signInOffline(email, password);
|
|
71
|
+
* if (result.success) {
|
|
72
|
+
* router.push('/dashboard');
|
|
73
|
+
* } else if (result.reason === 'no_credentials') {
|
|
74
|
+
* showError('You must log in online at least once before using offline mode.');
|
|
75
|
+
* } else {
|
|
76
|
+
* showError('Incorrect email or password.');
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @see {@link getOfflineLoginInfo} to check if offline credentials are available.
|
|
81
|
+
* @see {@link verifyOfflineCredentials} for the underlying verification logic.
|
|
82
|
+
* @see {@link createOfflineSession} for session creation details.
|
|
20
83
|
*/
|
|
21
84
|
export declare function signInOffline(email: string, password: string): Promise<OfflineLoginResult>;
|
|
22
85
|
/**
|
|
23
86
|
* Get non-sensitive display info about cached offline credentials.
|
|
24
|
-
*
|
|
25
|
-
* Used by login page to show "
|
|
87
|
+
*
|
|
88
|
+
* Used by the login page to show an "offline mode" UI with the cached
|
|
89
|
+
* user's name and email (e.g., "Sign in as jane@example.com").
|
|
90
|
+
*
|
|
91
|
+
* Returns `null` if no credentials are cached, meaning the user has never
|
|
92
|
+
* logged in online on this device and offline login is unavailable.
|
|
93
|
+
*
|
|
94
|
+
* @returns An object containing `hasCredentials`, `email`, `firstName`, and
|
|
95
|
+
* `lastName`, or `null` if no credentials exist.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const info = await getOfflineLoginInfo();
|
|
100
|
+
* if (info?.hasCredentials) {
|
|
101
|
+
* showOfflineLoginUI(info.email, info.firstName);
|
|
102
|
+
* } else {
|
|
103
|
+
* showOnlineOnlyMessage();
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @see {@link signInOffline} to perform the actual offline login.
|
|
26
108
|
*/
|
|
27
109
|
export declare function getOfflineLoginInfo(): Promise<{
|
|
110
|
+
/** Whether cached credentials exist on this device. */
|
|
28
111
|
hasCredentials: boolean;
|
|
112
|
+
/** The cached user's email address. */
|
|
29
113
|
email?: string;
|
|
114
|
+
/** The cached user's first name (from profile). */
|
|
30
115
|
firstName?: string;
|
|
116
|
+
/** The cached user's last name (from profile). */
|
|
31
117
|
lastName?: string;
|
|
32
118
|
} | null>;
|
|
33
119
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"offlineLogin.d.ts","sourceRoot":"","sources":["../../src/auth/offlineLogin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"offlineLogin.d.ts","sourceRoot":"","sources":["../../src/auth/offlineLogin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAUH;;;;;;GAMG;AACH,UAAU,kBAAkB;IAC1B,2CAA2C;IAC3C,OAAO,EAAE,OAAO,CAAC;IAEjB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EACH,gBAAgB,GAChB,oBAAoB,GACpB,eAAe,GACf,gBAAgB,GAChB,mBAAmB,GACnB,gBAAgB,CAAC;CACtB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAsChG;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC;IACnD,uDAAuD;IACvD,cAAc,EAAE,OAAO,CAAC;IACxB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,IAAI,CAAC,CAkBR"}
|