mnfst 0.5.136 → 0.5.138
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/lib/manifest.appwrite.auth.js +562 -50
- package/lib/manifest.chart.css +49 -0
- package/lib/manifest.charts.js +155 -3
- package/lib/manifest.css +271 -0
- package/lib/manifest.data.js +23 -3
- package/lib/manifest.integrity.json +3 -3
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.schema.json +11 -2
- package/package.json +9 -9
|
@@ -69,12 +69,14 @@ async function getAppwriteConfig() {
|
|
|
69
69
|
// Get auth methods from config (defaults to ["magic", "oauth"] if not specified)
|
|
70
70
|
const authMethods = appwriteConfig.auth?.methods || ["magic", "oauth"];
|
|
71
71
|
|
|
72
|
-
// Guest session support: "guest-auto" = automatic, "guest-manual" = manual only
|
|
73
|
-
|
|
72
|
+
// Guest session support: "guest"/"guest-auto" = automatic, "guest-manual" = manual only.
|
|
73
|
+
// ("guest" is the schema's documented spelling; "guest-auto" is accepted as a synonym.)
|
|
74
|
+
const guestAuto = authMethods.includes("guest") || authMethods.includes("guest-auto");
|
|
74
75
|
const guestManual = authMethods.includes("guest-manual");
|
|
75
76
|
const hasGuest = guestAuto || guestManual;
|
|
76
77
|
|
|
77
78
|
const magicEnabled = authMethods.includes("magic");
|
|
79
|
+
const otpEnabled = authMethods.includes("otp");
|
|
78
80
|
const oauthEnabled = authMethods.includes("oauth");
|
|
79
81
|
|
|
80
82
|
// Teams support: presence of teams object enables it
|
|
@@ -82,6 +84,15 @@ async function getAppwriteConfig() {
|
|
|
82
84
|
const permanentTeams = appwriteConfig.auth?.teams?.permanent || null; // Array of team names (immutable)
|
|
83
85
|
const templateTeams = appwriteConfig.auth?.teams?.template || null; // Array of team names (can be deleted and reapplied)
|
|
84
86
|
const teamsPollInterval = appwriteConfig.auth?.teams?.pollInterval || null; // Polling interval in milliseconds (null = disabled)
|
|
87
|
+
const guestTeams = !!appwriteConfig.auth?.teams?.guests; // Seed default teams for guest (anonymous) sessions
|
|
88
|
+
|
|
89
|
+
// Guest upgrade: preserve the anonymous account (and its teams) when a guest signs in,
|
|
90
|
+
// rather than discarding it and minting a fresh user. Supported by magic links and OAuth;
|
|
91
|
+
// email OTP cannot convert anonymous accounts (Appwrite limitation). Defaults to guestTeams,
|
|
92
|
+
// since orphaning guest-created teams on signup is the failure mode guest teams introduce.
|
|
93
|
+
const guestUpgrade = appwriteConfig.auth?.guestUpgrade !== undefined
|
|
94
|
+
? !!appwriteConfig.auth.guestUpgrade
|
|
95
|
+
: guestTeams;
|
|
85
96
|
|
|
86
97
|
// Default roles: permanent (cannot be deleted) and template (can be deleted)
|
|
87
98
|
// These are objects mapping role names to permissions: { "Admin": ["inviteMembers", ...] }
|
|
@@ -97,6 +108,11 @@ async function getAppwriteConfig() {
|
|
|
97
108
|
// Creator role: string reference to a role in memberRoles (role creator gets by default)
|
|
98
109
|
const creatorRole = appwriteConfig.auth?.creatorRole || null;
|
|
99
110
|
|
|
111
|
+
// Guest migration: id of the deployed guest-migration Appwrite Function. When set,
|
|
112
|
+
// a guest's teams are carried over to the account they sign into via OTP (which
|
|
113
|
+
// Appwrite can't convert in place). See templates/guest-migration-function.
|
|
114
|
+
const guestMigrationFunctionId = appwriteConfig.auth?.guestMigration?.functionId || null;
|
|
115
|
+
|
|
100
116
|
return {
|
|
101
117
|
endpoint,
|
|
102
118
|
projectId,
|
|
@@ -107,11 +123,15 @@ async function getAppwriteConfig() {
|
|
|
107
123
|
guestManual: guestManual,
|
|
108
124
|
anonymous: guestAuto, // For backwards compatibility with existing code
|
|
109
125
|
magic: magicEnabled,
|
|
126
|
+
otp: otpEnabled, // Email one-time-passcode authentication
|
|
110
127
|
oauth: oauthEnabled,
|
|
111
128
|
teams: teamsEnabled,
|
|
112
129
|
permanentTeams: permanentTeams, // Array of team names (cannot be deleted)
|
|
113
130
|
templateTeams: templateTeams, // Array of team names (can be deleted and reapplied)
|
|
114
131
|
teamsPollInterval: teamsPollInterval, // Polling interval in milliseconds (null = disabled)
|
|
132
|
+
guestTeams: guestTeams, // Seed default teams for guest (anonymous) sessions
|
|
133
|
+
guestUpgrade: guestUpgrade, // Preserve guest account + teams on sign-in (magic/oauth)
|
|
134
|
+
guestMigrationFunctionId: guestMigrationFunctionId, // Carry guest teams to the OTP account via this function
|
|
115
135
|
memberRoles: memberRoles, // Role definitions: { "RoleName": ["permission1", "permission2"] }
|
|
116
136
|
permanentRoles: permanentRoles, // Object: { "RoleName": ["permission1", ...] } (cannot be deleted)
|
|
117
137
|
templateRoles: templateRoles, // Object: { "RoleName": ["permission1", ...] } (can be deleted)
|
|
@@ -161,6 +181,7 @@ async function getAppwriteClient() {
|
|
|
161
181
|
account: appwriteAccount,
|
|
162
182
|
teams: appwriteTeams,
|
|
163
183
|
users: appwriteUsers, // Add users service for fetching user details
|
|
184
|
+
functions: window.Appwrite?.Functions ? new window.Appwrite.Functions(appwriteClient) : null, // For guest-migration function calls
|
|
164
185
|
realtime: window.Appwrite?.Realtime ? new window.Appwrite.Realtime(appwriteClient) : null // Realtime service for subscriptions
|
|
165
186
|
};
|
|
166
187
|
}
|
|
@@ -223,6 +244,8 @@ function initializeAuthStore() {
|
|
|
223
244
|
store.session = state.session;
|
|
224
245
|
store.magicLinkSent = state.magicLinkSent || false;
|
|
225
246
|
store.magicLinkExpired = state.magicLinkExpired || false;
|
|
247
|
+
store.otpSent = state.otpSent || false;
|
|
248
|
+
store.otpExpired = state.otpExpired || false;
|
|
226
249
|
store.error = state.error;
|
|
227
250
|
}
|
|
228
251
|
} catch (error) {
|
|
@@ -241,6 +264,8 @@ function initializeAuthStore() {
|
|
|
241
264
|
session: sanitizeSessionForStorage(store.session),
|
|
242
265
|
magicLinkSent: store.magicLinkSent,
|
|
243
266
|
magicLinkExpired: store.magicLinkExpired,
|
|
267
|
+
otpSent: store.otpSent,
|
|
268
|
+
otpExpired: store.otpExpired,
|
|
244
269
|
error: store.error
|
|
245
270
|
};
|
|
246
271
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
@@ -258,6 +283,10 @@ function initializeAuthStore() {
|
|
|
258
283
|
error: null,
|
|
259
284
|
magicLinkSent: false,
|
|
260
285
|
magicLinkExpired: false,
|
|
286
|
+
otpSent: false, // Email OTP: a code has been emailed and is awaiting entry
|
|
287
|
+
otpExpired: false, // Email OTP: the entered code was wrong/expired
|
|
288
|
+
otpPhrase: null, // Email OTP: security phrase to display (when enabled)
|
|
289
|
+
_otpUserId: null, // Email OTP: userId returned by createEmailToken, used by verifyOTP
|
|
261
290
|
teams: [], // List of user's teams
|
|
262
291
|
currentTeam: null, // Currently selected/active team
|
|
263
292
|
_teamsPollInterval: null, // Interval ID for teams polling (deprecated, use realtime instead)
|
|
@@ -390,15 +419,20 @@ function initializeAuthStore() {
|
|
|
390
419
|
return null;
|
|
391
420
|
},
|
|
392
421
|
|
|
393
|
-
// Get authentication method (
|
|
422
|
+
// Get authentication method (anonymous, magic, otp, phone, oauth)
|
|
394
423
|
getMethod() {
|
|
395
424
|
if (!this.session) return null;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
425
|
+
// Appwrite session.provider: anonymous | magic-url | email (OTP) |
|
|
426
|
+
// token (OTP, some versions) | phone | oauth2 (or a specific provider).
|
|
427
|
+
switch (this.session.provider) {
|
|
428
|
+
case 'anonymous': return 'anonymous';
|
|
429
|
+
case 'magic-url': return 'magic';
|
|
430
|
+
case 'email': return 'otp'; // this plugin uses email tokens for OTP
|
|
431
|
+
case 'token': return 'otp';
|
|
432
|
+
case 'phone': return 'phone';
|
|
433
|
+
case 'oauth2': return 'oauth';
|
|
434
|
+
default: return this.session.provider ? 'oauth' : null;
|
|
435
|
+
}
|
|
402
436
|
},
|
|
403
437
|
|
|
404
438
|
// Get OAuth provider name (google, github, etc.) or null for non-OAuth methods
|
|
@@ -411,8 +445,10 @@ function initializeAuthStore() {
|
|
|
411
445
|
const sessionProvider = this.session.provider;
|
|
412
446
|
|
|
413
447
|
// For OAuth, return the stored provider name (google, github, etc.)
|
|
414
|
-
// session.provider returns "oauth2" generically, so we use _oauthProvider
|
|
415
|
-
|
|
448
|
+
// session.provider returns "oauth2" generically, so we use _oauthProvider.
|
|
449
|
+
// Only OAuth sessions have a provider name — magic/otp/phone/anonymous don't,
|
|
450
|
+
// so gate on getMethod() to avoid a pointless identities lookup for those.
|
|
451
|
+
if (this.getMethod() === 'oauth') {
|
|
416
452
|
// Try to get from store first, then localStorage, then sessionStorage
|
|
417
453
|
let provider = this._oauthProvider;
|
|
418
454
|
if (!provider) {
|
|
@@ -548,16 +584,17 @@ function initializeAuthStore() {
|
|
|
548
584
|
this.isAnonymous = false;
|
|
549
585
|
}
|
|
550
586
|
|
|
551
|
-
// Load teams if enabled and user is authenticated
|
|
552
|
-
|
|
587
|
+
// Load teams if enabled and user is authenticated. Guests (anonymous)
|
|
588
|
+
// only seed default teams when auth.teams.guests is enabled.
|
|
589
|
+
// Note: do NOT gate on this.listTeams here — _loadTeamsAndSeed waits
|
|
590
|
+
// for the teams module to attach (it can lose the startup race to init).
|
|
591
|
+
if (this.isAuthenticated && appwriteConfig?.teams
|
|
592
|
+
&& (!this.isAnonymous || appwriteConfig.guestTeams)) {
|
|
553
593
|
try {
|
|
554
|
-
await this.
|
|
555
|
-
// Auto-create default teams if enabled
|
|
556
|
-
if ((appwriteConfig.permanentTeams || appwriteConfig.templateTeams) && window.ManifestAppwriteAuthTeamsDefaults?.ensureDefaultTeams) {
|
|
557
|
-
await window.ManifestAppwriteAuthTeamsDefaults.ensureDefaultTeams(this);
|
|
558
|
-
}
|
|
594
|
+
await this._loadTeamsAndSeed(appwriteConfig);
|
|
559
595
|
} catch (teamsError) {
|
|
560
|
-
// Don't fail initialization if teams fail to load
|
|
596
|
+
// Don't fail initialization if teams fail to load, but surface why
|
|
597
|
+
console.warn('[Manifest Appwrite Auth] Failed to load/seed teams on restore:', teamsError);
|
|
561
598
|
}
|
|
562
599
|
}
|
|
563
600
|
} catch (error) {
|
|
@@ -589,6 +626,72 @@ function initializeAuthStore() {
|
|
|
589
626
|
}
|
|
590
627
|
},
|
|
591
628
|
|
|
629
|
+
// Clear all team-related state. Used when the active identity changes to a
|
|
630
|
+
// DIFFERENT user — e.g. a guest replaced by a fresh account on OTP sign-in
|
|
631
|
+
// (Appwrite can't convert anonymous → OTP). Without this, the previous user's
|
|
632
|
+
// currentTeam/teams leak into the new session and listTeams queries teams the
|
|
633
|
+
// new user can't access, producing 404s on prefs/memberships.
|
|
634
|
+
_resetTeamsState() {
|
|
635
|
+
this.teams = [];
|
|
636
|
+
this.currentTeam = null;
|
|
637
|
+
this.currentTeamMemberships = [];
|
|
638
|
+
this.deletedTemplateTeams = [];
|
|
639
|
+
this.deletedTemplateRoles = [];
|
|
640
|
+
this._teamImmutableCache = {};
|
|
641
|
+
if (this.stopTeamsRealtime) {
|
|
642
|
+
try { this.stopTeamsRealtime(); } catch (e) { /* ignore */ }
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
|
|
646
|
+
// Call the deployed guest-migration function (templates/guest-migration-function).
|
|
647
|
+
// The current Appwrite session authenticates the call — Appwrite forwards the
|
|
648
|
+
// user id to the function. Returns the parsed JSON response, or null on any
|
|
649
|
+
// failure (migration is best-effort: a failure must never block sign-in).
|
|
650
|
+
async _callGuestMigration(path, body) {
|
|
651
|
+
const appwriteConfig = await config.getAppwriteConfig();
|
|
652
|
+
const fnId = appwriteConfig?.guestMigrationFunctionId;
|
|
653
|
+
if (!fnId || !this._appwrite?.functions) {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
try {
|
|
657
|
+
const exec = await this._appwrite.functions.createExecution(
|
|
658
|
+
fnId, JSON.stringify(body || {}), false, path, 'POST'
|
|
659
|
+
);
|
|
660
|
+
const raw = exec?.responseBody ?? exec?.response ?? '';
|
|
661
|
+
try { return JSON.parse(raw); } catch (e) { return null; }
|
|
662
|
+
} catch (e) {
|
|
663
|
+
console.warn(`[Manifest Appwrite Auth] Guest migration ${path} failed:`, e.message);
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
// Load the user's teams and seed any configured default (permanent/template)
|
|
669
|
+
// teams. Shared by the guest, magic-link, OAuth, and init/restore paths.
|
|
670
|
+
async _loadTeamsAndSeed(appwriteConfig) {
|
|
671
|
+
const cfg = appwriteConfig || await config.getAppwriteConfig();
|
|
672
|
+
if (!cfg?.teams) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
// Startup race: init() (and an early requestGuest) can reach here before
|
|
676
|
+
// teams.core.js / teams.defaults.js have finished wiring listTeams +
|
|
677
|
+
// ensureDefaultTeams onto the store. Wait briefly for them rather than
|
|
678
|
+
// silently skipping (which left guests with no teams on reload).
|
|
679
|
+
const needsSeed = !!(cfg.permanentTeams || cfg.templateTeams);
|
|
680
|
+
const ready = () => typeof this.listTeams === 'function'
|
|
681
|
+
&& (!needsSeed || typeof window.ManifestAppwriteAuthTeamsDefaults?.ensureDefaultTeams === 'function');
|
|
682
|
+
for (let i = 0; i < 40 && !ready(); i++) {
|
|
683
|
+
await new Promise(r => setTimeout(r, 50));
|
|
684
|
+
}
|
|
685
|
+
if (typeof this.listTeams !== 'function') {
|
|
686
|
+
console.warn('[Manifest Appwrite Auth] Teams module never became ready; skipping team load/seed.');
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
await this.listTeams();
|
|
690
|
+
if (needsSeed && window.ManifestAppwriteAuthTeamsDefaults?.ensureDefaultTeams) {
|
|
691
|
+
await window.ManifestAppwriteAuthTeamsDefaults.ensureDefaultTeams(this);
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
|
|
592
695
|
// Manually create guest session (only works if guest-manual is enabled)
|
|
593
696
|
async createGuest() {
|
|
594
697
|
if (!this._guestManual) {
|
|
@@ -632,9 +735,16 @@ function initializeAuthStore() {
|
|
|
632
735
|
// Ignore
|
|
633
736
|
}
|
|
634
737
|
|
|
635
|
-
//
|
|
636
|
-
|
|
637
|
-
|
|
738
|
+
// Guests are full Appwrite sessions, so they can own teams. When
|
|
739
|
+
// auth.teams.guests is enabled, seed their default teams just like a
|
|
740
|
+
// signed-in user; otherwise keep the historical behavior (no teams).
|
|
741
|
+
const cfg = await config.getAppwriteConfig();
|
|
742
|
+
if (cfg?.guestTeams) {
|
|
743
|
+
await this._loadTeamsAndSeed(cfg);
|
|
744
|
+
} else {
|
|
745
|
+
this.teams = [];
|
|
746
|
+
this.currentTeam = null;
|
|
747
|
+
}
|
|
638
748
|
|
|
639
749
|
syncStateToStorage(this);
|
|
640
750
|
window.dispatchEvent(new CustomEvent('manifest:auth:anonymous', {
|
|
@@ -698,6 +808,12 @@ function initializeAuthStore() {
|
|
|
698
808
|
this.magicLinkSent = false;
|
|
699
809
|
this.magicLinkExpired = false;
|
|
700
810
|
|
|
811
|
+
// Clear email OTP flags
|
|
812
|
+
this.otpSent = false;
|
|
813
|
+
this.otpExpired = false;
|
|
814
|
+
this.otpPhrase = null;
|
|
815
|
+
this._otpUserId = null;
|
|
816
|
+
|
|
701
817
|
// Stop teams realtime subscription if active
|
|
702
818
|
if (this.stopTeamsRealtime) {
|
|
703
819
|
this.stopTeamsRealtime();
|
|
@@ -2601,9 +2717,11 @@ async function ensureDefaultTeams(store) {
|
|
|
2601
2717
|
|
|
2602
2718
|
if (result.success) {
|
|
2603
2719
|
createdTeams.push(result.team);
|
|
2720
|
+
} else {
|
|
2721
|
+
console.warn(`[Manifest Appwrite Auth] Could not seed permanent team "${teamName}":`, result.error);
|
|
2604
2722
|
}
|
|
2605
2723
|
} catch (error) {
|
|
2606
|
-
|
|
2724
|
+
console.warn(`[Manifest Appwrite Auth] Error seeding permanent team "${teamName}":`, error);
|
|
2607
2725
|
}
|
|
2608
2726
|
}
|
|
2609
2727
|
}
|
|
@@ -2643,9 +2761,11 @@ async function ensureDefaultTeams(store) {
|
|
|
2643
2761
|
|
|
2644
2762
|
if (result.success) {
|
|
2645
2763
|
createdTeams.push(result.team);
|
|
2764
|
+
} else {
|
|
2765
|
+
console.warn(`[Manifest Appwrite Auth] Could not seed template team "${teamName}":`, result.error);
|
|
2646
2766
|
}
|
|
2647
2767
|
} catch (error) {
|
|
2648
|
-
|
|
2768
|
+
console.warn(`[Manifest Appwrite Auth] Error seeding template team "${teamName}":`, error);
|
|
2649
2769
|
}
|
|
2650
2770
|
}
|
|
2651
2771
|
}
|
|
@@ -4031,6 +4151,11 @@ window.ManifestAppwriteAuthTeamsUserRoles = {
|
|
|
4031
4151
|
|
|
4032
4152
|
/* Auth teams - Membership operations */
|
|
4033
4153
|
|
|
4154
|
+
// The Users service is server-only (node-appwrite); it is never present on the
|
|
4155
|
+
// browser SDK, so member-email enrichment degrades gracefully. Warn once rather
|
|
4156
|
+
// than on every membership lookup to avoid flooding the console.
|
|
4157
|
+
let _usersServiceWarned = false;
|
|
4158
|
+
|
|
4034
4159
|
// Add membership methods to auth store
|
|
4035
4160
|
function initializeTeamsMembers() {
|
|
4036
4161
|
if (typeof Alpine === 'undefined') {
|
|
@@ -4176,7 +4301,10 @@ function initializeTeamsMembers() {
|
|
|
4176
4301
|
try {
|
|
4177
4302
|
// Check if users service is available
|
|
4178
4303
|
if (!this._appwrite || !this._appwrite.users || typeof this._appwrite.users.get !== 'function') {
|
|
4179
|
-
|
|
4304
|
+
if (!_usersServiceWarned) {
|
|
4305
|
+
_usersServiceWarned = true;
|
|
4306
|
+
console.warn('[Manifest Appwrite Auth] Users service unavailable on the browser SDK — member emails will not be enriched. (This is expected; logged once.)');
|
|
4307
|
+
}
|
|
4180
4308
|
} else {
|
|
4181
4309
|
const user = await this._appwrite.users.get({ userId: membership.userId });
|
|
4182
4310
|
if (user && user.email) {
|
|
@@ -4211,7 +4339,10 @@ function initializeTeamsMembers() {
|
|
|
4211
4339
|
try {
|
|
4212
4340
|
// Check if users service is available
|
|
4213
4341
|
if (!this._appwrite || !this._appwrite.users || typeof this._appwrite.users.get !== 'function') {
|
|
4214
|
-
|
|
4342
|
+
if (!_usersServiceWarned) {
|
|
4343
|
+
_usersServiceWarned = true;
|
|
4344
|
+
console.warn('[Manifest Appwrite Auth] Users service unavailable on the browser SDK — member emails will not be enriched. (This is expected; logged once.)');
|
|
4345
|
+
}
|
|
4215
4346
|
} else {
|
|
4216
4347
|
const user = await this._appwrite.users.get({ userId: membership.userId });
|
|
4217
4348
|
if (user && user.email) {
|
|
@@ -5503,6 +5634,17 @@ function initializeAnonymous() {
|
|
|
5503
5634
|
this.isAuthenticated = true;
|
|
5504
5635
|
this.isAnonymous = true;
|
|
5505
5636
|
|
|
5637
|
+
// Seed default teams for the guest when auth.teams.guests is enabled
|
|
5638
|
+
const appwriteConfig = await config.getAppwriteConfig();
|
|
5639
|
+
if (appwriteConfig?.guestTeams && this._loadTeamsAndSeed) {
|
|
5640
|
+
try {
|
|
5641
|
+
await this._loadTeamsAndSeed(appwriteConfig);
|
|
5642
|
+
} catch (teamsError) {
|
|
5643
|
+
// Don't fail guest creation if teams fail to load, but surface why
|
|
5644
|
+
console.warn('[Manifest Appwrite Auth] Failed to seed guest teams:', teamsError);
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5647
|
+
|
|
5506
5648
|
// Sync state to localStorage for cross-tab synchronization
|
|
5507
5649
|
if (this._syncStateToStorage) {
|
|
5508
5650
|
this._syncStateToStorage(this);
|
|
@@ -5601,9 +5743,17 @@ function initializeMagicLinks() {
|
|
|
5601
5743
|
|
|
5602
5744
|
const account = this._appwrite.account;
|
|
5603
5745
|
|
|
5746
|
+
// Guest upgrade: when enabled and we're currently a guest, pass the
|
|
5747
|
+
// anonymous user's own id so Appwrite links the email to that same
|
|
5748
|
+
// account (preserving its teams) rather than minting a fresh user.
|
|
5749
|
+
// Otherwise generate a unique id for a brand-new account.
|
|
5750
|
+
const magicUserId = (appwriteConfig?.guestUpgrade && this.isAnonymous && this.user?.$id)
|
|
5751
|
+
? this.user.$id
|
|
5752
|
+
: ((window.Appwrite?.ID?.unique) ? window.Appwrite.ID.unique() : 'unique()');
|
|
5753
|
+
|
|
5604
5754
|
// Try createMagicURLSession first (standard method)
|
|
5605
5755
|
if (typeof account.createMagicURLSession === 'function') {
|
|
5606
|
-
const token = await account.createMagicURLSession(
|
|
5756
|
+
const token = await account.createMagicURLSession(magicUserId, email, cleanRedirectUrl);
|
|
5607
5757
|
this.magicLinkSent = true;
|
|
5608
5758
|
this.magicLinkExpired = false;
|
|
5609
5759
|
this.error = null;
|
|
@@ -5615,7 +5765,7 @@ function initializeMagicLinks() {
|
|
|
5615
5765
|
|
|
5616
5766
|
// Fallback: try createMagicURLToken (alternative method name)
|
|
5617
5767
|
if (typeof account.createMagicURLToken === 'function') {
|
|
5618
|
-
const token = await account.createMagicURLToken(
|
|
5768
|
+
const token = await account.createMagicURLToken(magicUserId, email, redirectUrl);
|
|
5619
5769
|
this.magicLinkSent = true;
|
|
5620
5770
|
this.magicLinkExpired = false;
|
|
5621
5771
|
this.error = null;
|
|
@@ -5758,8 +5908,18 @@ function initializeMagicLinks() {
|
|
|
5758
5908
|
this.magicLinkSent = false;
|
|
5759
5909
|
|
|
5760
5910
|
try {
|
|
5761
|
-
|
|
5762
|
-
|
|
5911
|
+
const appwriteConfig = await config.getAppwriteConfig();
|
|
5912
|
+
const upgradingGuest = !!(appwriteConfig?.guestUpgrade && this.isAnonymous);
|
|
5913
|
+
// A guest being replaced (not upgraded) by a different account — its
|
|
5914
|
+
// team state must be cleared before loading the new user's teams.
|
|
5915
|
+
const replacingGuest = this.isAnonymous && !upgradingGuest;
|
|
5916
|
+
|
|
5917
|
+
// Delete the existing anonymous session first — UNLESS we're upgrading
|
|
5918
|
+
// the guest in place. For an upgrade the magic token was created against
|
|
5919
|
+
// the anonymous user's own id, so createSession converts that same
|
|
5920
|
+
// account (keeping its teams); deleting it first would orphan the teams
|
|
5921
|
+
// and force a brand-new user.
|
|
5922
|
+
if (this.session && this.isAnonymous && !upgradingGuest) {
|
|
5763
5923
|
try {
|
|
5764
5924
|
await this._appwrite.account.deleteSession(this.session.$id);
|
|
5765
5925
|
} catch (deleteError) {
|
|
@@ -5767,8 +5927,20 @@ function initializeMagicLinks() {
|
|
|
5767
5927
|
}
|
|
5768
5928
|
}
|
|
5769
5929
|
|
|
5770
|
-
// Create session from magic link credentials
|
|
5771
|
-
|
|
5930
|
+
// Create session from magic link credentials. When upgrading a guest the
|
|
5931
|
+
// anonymous session may still be active; Appwrite can reject the duplicate
|
|
5932
|
+
// with a "prohibited" error, in which case the account is already upgraded
|
|
5933
|
+
// and we just reuse the current session.
|
|
5934
|
+
let session;
|
|
5935
|
+
try {
|
|
5936
|
+
session = await this._appwrite.account.createSession(userId, secret);
|
|
5937
|
+
} catch (createError) {
|
|
5938
|
+
if (upgradingGuest && createError.message?.includes('prohibited')) {
|
|
5939
|
+
session = await this._appwrite.account.getSession('current');
|
|
5940
|
+
} else {
|
|
5941
|
+
throw createError;
|
|
5942
|
+
}
|
|
5943
|
+
}
|
|
5772
5944
|
this.session = session;
|
|
5773
5945
|
this.user = await this._appwrite.account.get();
|
|
5774
5946
|
this.isAuthenticated = true;
|
|
@@ -5784,20 +5956,21 @@ function initializeMagicLinks() {
|
|
|
5784
5956
|
// Ignore
|
|
5785
5957
|
}
|
|
5786
5958
|
|
|
5959
|
+
// Replacing a guest with a different account: drop the guest's stale
|
|
5960
|
+
// team state so listTeams doesn't query teams the new user can't access.
|
|
5961
|
+
if (replacingGuest && this._resetTeamsState) {
|
|
5962
|
+
this._resetTeamsState();
|
|
5963
|
+
}
|
|
5964
|
+
|
|
5787
5965
|
// Sync state
|
|
5788
5966
|
if (this._syncStateToStorage) {
|
|
5789
5967
|
this._syncStateToStorage(this);
|
|
5790
5968
|
}
|
|
5791
5969
|
|
|
5792
|
-
// Load teams if enabled
|
|
5793
|
-
const appwriteConfig = await config.getAppwriteConfig();
|
|
5970
|
+
// Load teams if enabled (and seed any configured default teams)
|
|
5794
5971
|
if (appwriteConfig?.teams && this.listTeams) {
|
|
5795
5972
|
try {
|
|
5796
|
-
await this.
|
|
5797
|
-
// Auto-create default teams if enabled
|
|
5798
|
-
if ((appwriteConfig.permanentTeams || appwriteConfig.templateTeams) && window.ManifestAppwriteAuthTeamsDefaults?.ensureDefaultTeams) {
|
|
5799
|
-
await window.ManifestAppwriteAuthTeamsDefaults.ensureDefaultTeams(this);
|
|
5800
|
-
}
|
|
5973
|
+
await this._loadTeamsAndSeed(appwriteConfig);
|
|
5801
5974
|
} catch (teamsError) {
|
|
5802
5975
|
console.warn('[Manifest Appwrite Auth] Failed to load teams after magic link login:', teamsError);
|
|
5803
5976
|
// Don't fail login if teams fail to load
|
|
@@ -5981,6 +6154,334 @@ window.ManifestAppwriteAuthMagicLinks = {
|
|
|
5981
6154
|
handleCallbacks: handleMagicLinkCallbacks
|
|
5982
6155
|
};
|
|
5983
6156
|
|
|
6157
|
+
/* Auth email OTP (one-time passcode) */
|
|
6158
|
+
|
|
6159
|
+
// Email OTP is a two-step, in-page flow (no redirect, unlike magic links):
|
|
6160
|
+
// 1. createEmailOTP(email) -> Appwrite emails a 6-digit code, returns a userId
|
|
6161
|
+
// 2. verifyOTP(code) -> createSession(userId, code) completes login
|
|
6162
|
+
// Because there is no URL round-trip, this module never touches users.callbacks.js.
|
|
6163
|
+
//
|
|
6164
|
+
// NOTE: Appwrite does NOT support converting an anonymous (guest) session via email
|
|
6165
|
+
// OTP — only magic links, phone, email/password, and OAuth can do that. So when a
|
|
6166
|
+
// guest verifies an OTP we mint a fresh account (the anonymous session is deleted),
|
|
6167
|
+
// which discards any guest-created teams. Use magic links if you need guest upgrade.
|
|
6168
|
+
|
|
6169
|
+
function initializeEmailOTP() {
|
|
6170
|
+
if (typeof Alpine === 'undefined') {
|
|
6171
|
+
return;
|
|
6172
|
+
}
|
|
6173
|
+
|
|
6174
|
+
const config = window.ManifestAppwriteAuthConfig;
|
|
6175
|
+
if (!config) {
|
|
6176
|
+
return;
|
|
6177
|
+
}
|
|
6178
|
+
|
|
6179
|
+
// Resolve an email string from the same range of inputs sendMagicLink accepts:
|
|
6180
|
+
// an input element, a selector, an Alpine { email } object, a bare string, or
|
|
6181
|
+
// nothing (auto-find the nearest email input). Returns { email, inputEl, dataObj }.
|
|
6182
|
+
function resolveEmailInput(emailInputOrRef) {
|
|
6183
|
+
let email = null;
|
|
6184
|
+
let inputEl = null;
|
|
6185
|
+
let dataObj = null;
|
|
6186
|
+
|
|
6187
|
+
if (emailInputOrRef === undefined || emailInputOrRef === null) {
|
|
6188
|
+
let eventTarget = (typeof window !== 'undefined' && window.event) ? window.event.target : null;
|
|
6189
|
+
if (eventTarget) {
|
|
6190
|
+
const form = eventTarget.closest('form');
|
|
6191
|
+
const scope = form || eventTarget.parentElement;
|
|
6192
|
+
if (scope) {
|
|
6193
|
+
inputEl = scope.querySelector('input[type="email"]');
|
|
6194
|
+
if (inputEl) email = inputEl.value;
|
|
6195
|
+
}
|
|
6196
|
+
}
|
|
6197
|
+
if (!inputEl) {
|
|
6198
|
+
inputEl = document.querySelector('input[type="email"]');
|
|
6199
|
+
if (inputEl) email = inputEl.value;
|
|
6200
|
+
}
|
|
6201
|
+
} else if (typeof emailInputOrRef === 'string') {
|
|
6202
|
+
try {
|
|
6203
|
+
const element = document.querySelector(emailInputOrRef);
|
|
6204
|
+
if (element && element.tagName === 'INPUT' && element.type === 'email') {
|
|
6205
|
+
inputEl = element;
|
|
6206
|
+
email = element.value;
|
|
6207
|
+
} else {
|
|
6208
|
+
email = emailInputOrRef; // Treat as a direct email string
|
|
6209
|
+
}
|
|
6210
|
+
} catch (e) {
|
|
6211
|
+
email = emailInputOrRef; // Invalid selector -> treat as email string
|
|
6212
|
+
}
|
|
6213
|
+
} else if (emailInputOrRef && typeof emailInputOrRef === 'object') {
|
|
6214
|
+
if (emailInputOrRef.tagName === 'INPUT' || emailInputOrRef.matches?.('input[type="email"]')) {
|
|
6215
|
+
inputEl = emailInputOrRef;
|
|
6216
|
+
email = inputEl.value;
|
|
6217
|
+
} else if ('email' in emailInputOrRef) {
|
|
6218
|
+
email = emailInputOrRef.email;
|
|
6219
|
+
dataObj = emailInputOrRef;
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
6222
|
+
|
|
6223
|
+
return { email, inputEl, dataObj };
|
|
6224
|
+
}
|
|
6225
|
+
|
|
6226
|
+
const waitForStore = () => {
|
|
6227
|
+
const store = Alpine.store('auth');
|
|
6228
|
+
if (store && !store.createEmailOTP) {
|
|
6229
|
+
// Step 1: request a one-time passcode by email.
|
|
6230
|
+
// Pass { phrase: true } to enable Appwrite's security phrase (anti-phishing);
|
|
6231
|
+
// when enabled the phrase is stored on the store as `otpPhrase` for display.
|
|
6232
|
+
store.createEmailOTP = async function (email, options = {}) {
|
|
6233
|
+
if (!this._appwrite) {
|
|
6234
|
+
this._appwrite = await config.getAppwriteClient();
|
|
6235
|
+
}
|
|
6236
|
+
if (!this._appwrite) {
|
|
6237
|
+
return { success: false, error: 'Appwrite not configured' };
|
|
6238
|
+
}
|
|
6239
|
+
|
|
6240
|
+
// Don't allow OTP request if already signed in (non-anonymous)
|
|
6241
|
+
if (this.isAuthenticated && !this.isAnonymous) {
|
|
6242
|
+
return { success: false, error: 'Already signed in. Please logout first.' };
|
|
6243
|
+
}
|
|
6244
|
+
|
|
6245
|
+
const appwriteConfig = await config.getAppwriteConfig();
|
|
6246
|
+
if (appwriteConfig && !appwriteConfig.otp) {
|
|
6247
|
+
return { success: false, error: 'Email OTP authentication is not enabled' };
|
|
6248
|
+
}
|
|
6249
|
+
|
|
6250
|
+
// Appwrite can't convert an anonymous account via OTP. Warn (once) so guest
|
|
6251
|
+
// teams aren't silently lost; the login still proceeds as a fresh account.
|
|
6252
|
+
if (this.isAnonymous && appwriteConfig?.guestUpgrade) {
|
|
6253
|
+
console.warn('[Manifest Appwrite Auth] Email OTP cannot upgrade a guest account (Appwrite limitation); the guest session and any guest-created teams will be replaced. Use magic links for guest upgrade.');
|
|
6254
|
+
}
|
|
6255
|
+
|
|
6256
|
+
const account = this._appwrite.account;
|
|
6257
|
+
if (typeof account.createEmailToken !== 'function') {
|
|
6258
|
+
return {
|
|
6259
|
+
success: false,
|
|
6260
|
+
error: 'Email OTP method not available. Please ensure you are using a recent Appwrite SDK.'
|
|
6261
|
+
};
|
|
6262
|
+
}
|
|
6263
|
+
|
|
6264
|
+
this.inProgress = true;
|
|
6265
|
+
this.error = null;
|
|
6266
|
+
this.otpExpired = false;
|
|
6267
|
+
|
|
6268
|
+
try {
|
|
6269
|
+
const uniqueId = (window.Appwrite?.ID?.unique) ? window.Appwrite.ID.unique() : 'unique()';
|
|
6270
|
+
// Third arg toggles Appwrite's security phrase feature.
|
|
6271
|
+
const token = await account.createEmailToken(uniqueId, email, options.phrase === true);
|
|
6272
|
+
|
|
6273
|
+
// Stash the userId Appwrite assigned; verifyOTP needs it to complete login.
|
|
6274
|
+
this._otpUserId = token.userId;
|
|
6275
|
+
this.otpPhrase = token.phrase || null;
|
|
6276
|
+
this.otpSent = true;
|
|
6277
|
+
this.otpExpired = false;
|
|
6278
|
+
this.error = null;
|
|
6279
|
+
|
|
6280
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:otp-sent', {
|
|
6281
|
+
detail: { email, phrase: this.otpPhrase }
|
|
6282
|
+
}));
|
|
6283
|
+
|
|
6284
|
+
return { success: true, message: 'OTP sent to email', phrase: this.otpPhrase };
|
|
6285
|
+
} catch (error) {
|
|
6286
|
+
// Appwrite returns 501 Not Implemented when Email OTP isn't enabled
|
|
6287
|
+
// for the project. Surface an actionable message instead of the raw error.
|
|
6288
|
+
const code = error.code || error.statusCode;
|
|
6289
|
+
const notEnabled = code === 501 || /not implemented/i.test(error.message || '');
|
|
6290
|
+
this.error = notEnabled
|
|
6291
|
+
? 'Email OTP is not enabled for this Appwrite project. Enable it under Auth → Settings.'
|
|
6292
|
+
: error.message;
|
|
6293
|
+
this.otpSent = false;
|
|
6294
|
+
this.otpExpired = false;
|
|
6295
|
+
return { success: false, error: this.error };
|
|
6296
|
+
} finally {
|
|
6297
|
+
this.inProgress = false;
|
|
6298
|
+
}
|
|
6299
|
+
};
|
|
6300
|
+
|
|
6301
|
+
// Convenience: resolve the email from an input/selector/object/string and send.
|
|
6302
|
+
// Clears the email input on success (mirrors sendMagicLink).
|
|
6303
|
+
store.sendEmailOTP = async function (emailInputOrRef, options = {}) {
|
|
6304
|
+
const { email, inputEl, dataObj } = resolveEmailInput(emailInputOrRef);
|
|
6305
|
+
|
|
6306
|
+
if (!email || !email.trim()) {
|
|
6307
|
+
return { success: false, error: 'Email is required' };
|
|
6308
|
+
}
|
|
6309
|
+
|
|
6310
|
+
const result = await this.createEmailOTP(email.trim(), options);
|
|
6311
|
+
|
|
6312
|
+
if (result.success) {
|
|
6313
|
+
Promise.resolve().then(() => {
|
|
6314
|
+
if (inputEl) {
|
|
6315
|
+
inputEl.value = '';
|
|
6316
|
+
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
|
6317
|
+
} else if (dataObj) {
|
|
6318
|
+
dataObj.email = '';
|
|
6319
|
+
}
|
|
6320
|
+
});
|
|
6321
|
+
}
|
|
6322
|
+
|
|
6323
|
+
return result;
|
|
6324
|
+
};
|
|
6325
|
+
|
|
6326
|
+
// Step 2: verify the code and create the session.
|
|
6327
|
+
store.verifyOTP = async function (code) {
|
|
6328
|
+
if (!this._appwrite) {
|
|
6329
|
+
this._appwrite = await config.getAppwriteClient();
|
|
6330
|
+
}
|
|
6331
|
+
if (!this._appwrite) {
|
|
6332
|
+
return { success: false, error: 'Appwrite not configured' };
|
|
6333
|
+
}
|
|
6334
|
+
if (!this._otpUserId) {
|
|
6335
|
+
return { success: false, error: 'Request a code first' };
|
|
6336
|
+
}
|
|
6337
|
+
if (!code || !String(code).trim()) {
|
|
6338
|
+
return { success: false, error: 'Code is required' };
|
|
6339
|
+
}
|
|
6340
|
+
|
|
6341
|
+
this.inProgress = true;
|
|
6342
|
+
this.error = null;
|
|
6343
|
+
|
|
6344
|
+
try {
|
|
6345
|
+
const appwriteConfig = await config.getAppwriteConfig();
|
|
6346
|
+
const wasGuest = this.isAnonymous;
|
|
6347
|
+
|
|
6348
|
+
// Guest team carryover: OTP can't convert the anonymous account, so we
|
|
6349
|
+
// migrate its teams to the new account instead. Issue the migration
|
|
6350
|
+
// ticket NOW, while the guest session is still authenticated — it's
|
|
6351
|
+
// redeemed after the new session exists. Best-effort; never blocks login.
|
|
6352
|
+
let migrationTicket = null;
|
|
6353
|
+
if (wasGuest && appwriteConfig?.guestMigrationFunctionId && this._callGuestMigration) {
|
|
6354
|
+
const prep = await this._callGuestMigration('/prepare', {});
|
|
6355
|
+
if (prep?.ok && prep.ticket) migrationTicket = prep.ticket;
|
|
6356
|
+
}
|
|
6357
|
+
|
|
6358
|
+
// Appwrite can't convert anonymous accounts via OTP, so delete the
|
|
6359
|
+
// guest session first to avoid a "session prohibited" conflict.
|
|
6360
|
+
if (this.session && this.isAnonymous) {
|
|
6361
|
+
try {
|
|
6362
|
+
await this._appwrite.account.deleteSession(this.session.$id);
|
|
6363
|
+
} catch (deleteError) {
|
|
6364
|
+
// Could not delete anonymous session
|
|
6365
|
+
}
|
|
6366
|
+
}
|
|
6367
|
+
|
|
6368
|
+
const session = await this._appwrite.account.createSession(this._otpUserId, String(code).trim());
|
|
6369
|
+
this.session = session;
|
|
6370
|
+
this.user = await this._appwrite.account.get();
|
|
6371
|
+
this.isAuthenticated = true;
|
|
6372
|
+
this.isAnonymous = false;
|
|
6373
|
+
this.otpSent = false;
|
|
6374
|
+
this.otpExpired = false;
|
|
6375
|
+
this.otpPhrase = null;
|
|
6376
|
+
this._otpUserId = null;
|
|
6377
|
+
this.error = null;
|
|
6378
|
+
|
|
6379
|
+
// OTP replaces any prior guest with a different account (no conversion),
|
|
6380
|
+
// so clear the guest's team state before loading the new user's teams —
|
|
6381
|
+
// otherwise the stale currentTeam triggers 404s in listTeams.
|
|
6382
|
+
if (this._resetTeamsState) {
|
|
6383
|
+
this._resetTeamsState();
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6386
|
+
// Redeem the migration ticket as the new account: carries the guest's
|
|
6387
|
+
// teams over. Best-effort — a failure leaves the guest's teams for GC
|
|
6388
|
+
// but never blocks the (already successful) sign-in.
|
|
6389
|
+
if (migrationTicket && this._callGuestMigration) {
|
|
6390
|
+
await this._callGuestMigration('/commit', { ticket: migrationTicket });
|
|
6391
|
+
}
|
|
6392
|
+
|
|
6393
|
+
if (this._syncStateToStorage) {
|
|
6394
|
+
this._syncStateToStorage(this);
|
|
6395
|
+
}
|
|
6396
|
+
|
|
6397
|
+
// Load teams + seed any configured default teams for the new account
|
|
6398
|
+
if (appwriteConfig?.teams && this.listTeams) {
|
|
6399
|
+
try {
|
|
6400
|
+
await this._loadTeamsAndSeed(appwriteConfig);
|
|
6401
|
+
} catch (teamsError) {
|
|
6402
|
+
console.warn('[Manifest Appwrite Auth] Failed to load teams after OTP login:', teamsError);
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6405
|
+
|
|
6406
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:login', {
|
|
6407
|
+
detail: { user: this.user }
|
|
6408
|
+
}));
|
|
6409
|
+
|
|
6410
|
+
return { success: true, user: this.user };
|
|
6411
|
+
} catch (error) {
|
|
6412
|
+
const errorMessage = error.message || '';
|
|
6413
|
+
const errorCode = error.code || error.statusCode || '';
|
|
6414
|
+
const isExpiredOrInvalid = errorMessage && (
|
|
6415
|
+
errorMessage.includes('expired') ||
|
|
6416
|
+
errorMessage.includes('Invalid token') ||
|
|
6417
|
+
errorMessage.includes('invalid') ||
|
|
6418
|
+
errorMessage.includes('not found') ||
|
|
6419
|
+
errorCode === 401 || errorCode === 404
|
|
6420
|
+
);
|
|
6421
|
+
|
|
6422
|
+
this.otpExpired = !!isExpiredOrInvalid;
|
|
6423
|
+
this.error = isExpiredOrInvalid ? null : error.message;
|
|
6424
|
+
this.isAuthenticated = false;
|
|
6425
|
+
this.isAnonymous = false;
|
|
6426
|
+
|
|
6427
|
+
if (this._syncStateToStorage) {
|
|
6428
|
+
this._syncStateToStorage(this);
|
|
6429
|
+
}
|
|
6430
|
+
|
|
6431
|
+
return { success: false, error: error.message };
|
|
6432
|
+
} finally {
|
|
6433
|
+
this.inProgress = false;
|
|
6434
|
+
}
|
|
6435
|
+
};
|
|
6436
|
+
|
|
6437
|
+
// Convenience: resolve the code from an input/selector/object/string and verify.
|
|
6438
|
+
store.submitOTP = async function (codeInputOrRef) {
|
|
6439
|
+
let code = null;
|
|
6440
|
+
if (codeInputOrRef === undefined || codeInputOrRef === null) {
|
|
6441
|
+
const el = document.querySelector('input[name="otp"], input[autocomplete="one-time-code"], input[inputmode="numeric"]');
|
|
6442
|
+
if (el) code = el.value;
|
|
6443
|
+
} else if (typeof codeInputOrRef === 'string') {
|
|
6444
|
+
try {
|
|
6445
|
+
const el = document.querySelector(codeInputOrRef);
|
|
6446
|
+
code = (el && el.tagName === 'INPUT') ? el.value : codeInputOrRef;
|
|
6447
|
+
} catch (e) {
|
|
6448
|
+
code = codeInputOrRef;
|
|
6449
|
+
}
|
|
6450
|
+
} else if (codeInputOrRef && typeof codeInputOrRef === 'object') {
|
|
6451
|
+
if (codeInputOrRef.tagName === 'INPUT') {
|
|
6452
|
+
code = codeInputOrRef.value;
|
|
6453
|
+
} else if ('code' in codeInputOrRef) {
|
|
6454
|
+
code = codeInputOrRef.code;
|
|
6455
|
+
} else if ('otp' in codeInputOrRef) {
|
|
6456
|
+
code = codeInputOrRef.otp;
|
|
6457
|
+
}
|
|
6458
|
+
}
|
|
6459
|
+
|
|
6460
|
+
return await this.verifyOTP(code);
|
|
6461
|
+
};
|
|
6462
|
+
} else if (!store) {
|
|
6463
|
+
setTimeout(waitForStore, 50);
|
|
6464
|
+
}
|
|
6465
|
+
};
|
|
6466
|
+
|
|
6467
|
+
setTimeout(waitForStore, 100);
|
|
6468
|
+
}
|
|
6469
|
+
|
|
6470
|
+
// Initialize when Alpine is ready
|
|
6471
|
+
document.addEventListener('alpine:init', () => {
|
|
6472
|
+
try {
|
|
6473
|
+
initializeEmailOTP();
|
|
6474
|
+
} catch (error) {
|
|
6475
|
+
// Failed to initialize email OTP
|
|
6476
|
+
}
|
|
6477
|
+
});
|
|
6478
|
+
|
|
6479
|
+
// Export email OTP interface
|
|
6480
|
+
window.ManifestAppwriteAuthEmailOTP = {
|
|
6481
|
+
initialize: initializeEmailOTP
|
|
6482
|
+
};
|
|
6483
|
+
|
|
6484
|
+
|
|
5984
6485
|
/* Auth OAuth */
|
|
5985
6486
|
|
|
5986
6487
|
// Add OAuth methods to auth store
|
|
@@ -6022,8 +6523,10 @@ function initializeOAuth() {
|
|
|
6022
6523
|
|
|
6023
6524
|
// Delete any existing anonymous sessions before OAuth
|
|
6024
6525
|
// This prevents conflicts where anonymous sessions might interfere with OAuth
|
|
6025
|
-
// Appwrite will create a new account for OAuth if needed
|
|
6026
|
-
|
|
6526
|
+
// Appwrite will create a new account for OAuth if needed.
|
|
6527
|
+
// EXCEPTION: when guest upgrade is enabled we keep the anonymous session
|
|
6528
|
+
// active so Appwrite can link the OAuth identity to it (preserving teams).
|
|
6529
|
+
if (this.isAnonymous && this.session && !appwriteConfig?.guestUpgrade) {
|
|
6027
6530
|
try {
|
|
6028
6531
|
await this._appwrite.account.deleteSession(this.session.$id);
|
|
6029
6532
|
this.session = null;
|
|
@@ -6175,8 +6678,16 @@ function handleOAuthCallbacks() {
|
|
|
6175
6678
|
store.magicLinkSent = false;
|
|
6176
6679
|
|
|
6177
6680
|
try {
|
|
6178
|
-
|
|
6179
|
-
|
|
6681
|
+
const appwriteConfig = await window.ManifestAppwriteAuthConfig.getAppwriteConfig();
|
|
6682
|
+
const upgradingGuest = !!(appwriteConfig?.guestUpgrade && store.isAnonymous);
|
|
6683
|
+
// A guest being replaced (not upgraded) by a different account — its team
|
|
6684
|
+
// state must be cleared before loading the new user's teams.
|
|
6685
|
+
const replacingGuest = store.isAnonymous && !upgradingGuest;
|
|
6686
|
+
|
|
6687
|
+
// Delete the existing anonymous session first — UNLESS we're upgrading the
|
|
6688
|
+
// guest in place, in which case Appwrite linked the OAuth identity to that
|
|
6689
|
+
// account and the "prohibited" branch below reuses the upgraded session.
|
|
6690
|
+
if (store.session && store.isAnonymous && !upgradingGuest) {
|
|
6180
6691
|
try {
|
|
6181
6692
|
await store._appwrite.account.deleteSession(store.session.$id);
|
|
6182
6693
|
} catch (deleteError) {
|
|
@@ -6223,20 +6734,21 @@ function handleOAuthCallbacks() {
|
|
|
6223
6734
|
}
|
|
6224
6735
|
}
|
|
6225
6736
|
|
|
6737
|
+
// Replacing a guest with a different account: drop the guest's stale team
|
|
6738
|
+
// state so listTeams doesn't query teams the new user can't access.
|
|
6739
|
+
if (replacingGuest && store._resetTeamsState) {
|
|
6740
|
+
store._resetTeamsState();
|
|
6741
|
+
}
|
|
6742
|
+
|
|
6226
6743
|
// Sync state
|
|
6227
6744
|
if (store._syncStateToStorage) {
|
|
6228
6745
|
store._syncStateToStorage(store);
|
|
6229
6746
|
}
|
|
6230
6747
|
|
|
6231
|
-
// Load teams if enabled
|
|
6232
|
-
const appwriteConfig = await window.ManifestAppwriteAuthConfig.getAppwriteConfig();
|
|
6748
|
+
// Load teams if enabled (and seed any configured default teams)
|
|
6233
6749
|
if (appwriteConfig?.teams && store.listTeams) {
|
|
6234
6750
|
try {
|
|
6235
|
-
await store.
|
|
6236
|
-
// Auto-create default teams if enabled
|
|
6237
|
-
if ((appwriteConfig.permanentTeams || appwriteConfig.templateTeams) && window.ManifestAppwriteAuthTeamsDefaults?.ensureDefaultTeams) {
|
|
6238
|
-
await window.ManifestAppwriteAuthTeamsDefaults.ensureDefaultTeams(store);
|
|
6239
|
-
}
|
|
6751
|
+
await store._loadTeamsAndSeed(appwriteConfig);
|
|
6240
6752
|
} catch (teamsError) {
|
|
6241
6753
|
console.warn('[Manifest Appwrite Auth] Failed to load teams after OAuth login:', teamsError);
|
|
6242
6754
|
// Don't fail login if teams fail to load
|