Skip to content

Commit 6110c0c

Browse files
committed
fix
1 parent 0e963cd commit 6110c0c

5 files changed

Lines changed: 118 additions & 27 deletions

File tree

src/hooks.client.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as Sentry from '@sentry/sveltekit';
22
import { isCloud, isProd } from '$lib/system';
33
import { AppwriteException } from '@appwrite.io/console';
4+
import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification';
45
import type { HandleClientError } from '@sveltejs/kit';
56

67
Sentry.init({
@@ -12,7 +13,9 @@ Sentry.init({
1213
});
1314

1415
export const handleError: HandleClientError = ({ error, message, status }) => {
15-
console.error(error);
16+
if (!isVerifyEmailRedirectError(error)) {
17+
console.error(error);
18+
}
1619

1720
let type;
1821
if (error instanceof AppwriteException) {

src/lib/helpers/emailVerification.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import type { Models } from '@appwrite.io/console';
1+
import { AppwriteException, type Models } from '@appwrite.io/console';
22

33
const EMAIL_VERIFICATION_REQUIRED_ERROR_TYPE = 'user_email_not_verified';
4+
const CONSOLE_ACCOUNT_VERIFICATION_REQUIRED_TYPE = 'console_account_verification_required';
45

56
export function isEmailVerificationEnabled(
67
consoleVariables: Models.ConsoleVariables | undefined
@@ -15,3 +16,30 @@ export function isEmailVerificationEnabled(
1516
export function isEmailVerificationRequiredError(type: string | undefined): boolean {
1617
return type === EMAIL_VERIFICATION_REQUIRED_ERROR_TYPE;
1718
}
19+
20+
function matchesVerifyEmailGate(type: string | undefined, message: string | undefined): boolean {
21+
if (isEmailVerificationRequiredError(type)) return true;
22+
if (type === CONSOLE_ACCOUNT_VERIFICATION_REQUIRED_TYPE) return true;
23+
if (message?.includes('Console account verification is required')) return true;
24+
return false;
25+
}
26+
27+
/**
28+
* Console APIs may return `user_email_not_verified`, `console_account_verification_required`,
29+
* or a message-only error when email verification is enforced for the console account.
30+
*/
31+
export function isVerifyEmailRedirectError(error: unknown): boolean {
32+
if (error instanceof AppwriteException) {
33+
return matchesVerifyEmailGate(error.type, error.message);
34+
}
35+
if (typeof error === 'object' && error !== null && 'message' in error) {
36+
const msg = (error as { message: unknown }).message;
37+
if (typeof msg !== 'string') return false;
38+
const typ =
39+
'type' in error && typeof (error as { type: unknown }).type === 'string'
40+
? (error as { type: string }).type
41+
: undefined;
42+
return matchesVerifyEmailGate(typ, msg);
43+
}
44+
return false;
45+
}

src/routes/(console)/+error.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
import { base, resolve } from '$app/paths';
44
import { page } from '$app/state';
55
import { Button } from '$lib/elements/forms';
6-
import { isEmailVerificationRequiredError } from '$lib/helpers/emailVerification';
6+
import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification';
77
import { Container } from '$lib/layout';
88
import { Typography } from '@appwrite.io/pink-svelte';
99
1010
$effect(() => {
11-
if (isEmailVerificationRequiredError(page.error.type)) {
11+
if (isVerifyEmailRedirectError(page.error)) {
1212
goto(resolve('/verify-email'), { replaceState: true });
1313
}
1414
});

src/routes/(console)/+layout.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,71 @@
11
import { sdk } from '$lib/stores/sdk';
22
import { isCloud } from '$lib/system';
33
import type { LayoutLoad } from './$types';
4+
import type { Account } from '$lib/stores/user';
45
import { Dependencies } from '$lib/constants';
5-
import { AppwriteException, Platform, Query } from '@appwrite.io/console';
6+
import { Platform, Query, type Models } from '@appwrite.io/console';
67
import { makePlansMap } from '$lib/helpers/billing';
78
import { plansInfo as plansInfoStore } from '$lib/stores/billing';
89
import { normalizeConsoleVariables } from '$lib/helpers/domains';
910
import { syncServerTime } from '$lib/helpers/fingerprint';
1011
import { redirect } from '@sveltejs/kit';
1112
import { resolve } from '$app/paths';
12-
import { isEmailVerificationRequiredError } from '$lib/helpers/emailVerification';
13+
import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification';
1314

1415
export const load: LayoutLoad = async ({ depends, parent, url }) => {
15-
const { organizations, plansInfo } = await parent();
16+
const parentData = await parent();
17+
const { organizations, plansInfo } = parentData;
18+
const account = parentData.account as Account | undefined;
19+
20+
const { endpoint, project } = sdk.forConsole.client.config;
21+
const verifyEmailUrl = resolve('/verify-email');
22+
23+
// While unverified, several console APIs (not only teams) may return 401; avoid failing the layout.
24+
if (url.pathname === verifyEmailUrl && account && !account.emailVerification) {
25+
depends(Dependencies.RUNTIMES);
26+
depends(Dependencies.CONSOLE_VARIABLES);
27+
depends(Dependencies.ORGANIZATION);
28+
29+
const [preferences, rawConsoleVariables, versionData] = await Promise.all([
30+
sdk.forConsole.account.getPrefs(),
31+
sdk.forConsole.console.variables().catch(() => ({}) as Models.ConsoleVariables),
32+
fetch(`${endpoint}/health/version`, {
33+
headers: { 'X-Appwrite-Project': project as string }
34+
})
35+
.then(async (response) => {
36+
const dateHeader = response.headers.get('Date');
37+
const parsed = dateHeader ? new Date(dateHeader).getTime() : NaN;
38+
if (Number.isFinite(parsed)) {
39+
syncServerTime(Math.floor(parsed / 1000));
40+
}
41+
return response.json() as { version?: string };
42+
})
43+
.catch(() => null)
44+
]);
45+
46+
const consoleVariables = normalizeConsoleVariables(rawConsoleVariables);
47+
48+
plansInfoStore.set(plansInfo ?? null);
49+
50+
return {
51+
roles: [],
52+
scopes: [],
53+
preferences,
54+
currentOrgId: undefined,
55+
organizations,
56+
consoleVariables,
57+
allProjectsCount: 0,
58+
plansInfo: plansInfo ?? null,
59+
version: versionData?.version ?? null
60+
};
61+
}
1662

1763
depends(Dependencies.RUNTIMES);
1864
depends(Dependencies.CONSOLE_VARIABLES);
1965
depends(Dependencies.ORGANIZATION);
2066

21-
const { endpoint, project } = sdk.forConsole.client.config;
22-
const verifyEmailUrl = resolve('/verify-email');
2367
const shouldRedirectToVerifyEmail = (error: unknown) =>
24-
error instanceof AppwriteException &&
25-
isEmailVerificationRequiredError(error.type) &&
26-
url.pathname !== verifyEmailUrl;
68+
isVerifyEmailRedirectError(error) && url.pathname !== verifyEmailUrl;
2769

2870
const plansArrayPromise =
2971
plansInfo || !isCloud

src/routes/+layout.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,24 @@ import type { LayoutLoad } from './$types';
88
import { redirectTo } from './store';
99
import { resolve } from '$app/paths';
1010
import type { Account } from '$lib/stores/user';
11-
import { AppwriteException, Platform } from '@appwrite.io/console';
11+
import { AppwriteException, Platform, type Models } from '@appwrite.io/console';
1212
import { isCloud } from '$lib/system';
1313
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
1414
import { getTeamOrOrganizationList } from '$lib/stores/organization';
1515
import { makePlansMap } from '$lib/helpers/billing';
1616
import { plansInfo as plansInfoStore } from '$lib/stores/billing';
17-
import { isEmailVerificationRequiredError } from '$lib/helpers/emailVerification';
17+
import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification';
1818

1919
export const ssr = false;
2020

21+
const EMPTY_ORGANIZATIONS: Models.TeamList = { total: 0, teams: [] };
22+
2123
export const load: LayoutLoad = async ({ depends, url, route }) => {
2224
depends(Dependencies.ACCOUNT);
2325
depends(Dependencies.ORGANIZATIONS);
2426

27+
const verifyEmailPath = resolve('/verify-email');
28+
2529
const [account, error] = (await sdk.forConsole.account
2630
.get()
2731
.then((response) => [response, null])
@@ -33,6 +37,18 @@ export const load: LayoutLoad = async ({ depends, url, route }) => {
3337
}
3438

3539
if (account) {
40+
// `/v1/teams` (and org list on cloud) returns 401 until the console account is verified;
41+
// do not call that API on this route while still unverified.
42+
if (url.pathname === verifyEmailPath && !account.emailVerification) {
43+
const plansInfo = await getPlatformPlans();
44+
plansInfoStore.set(plansInfo);
45+
return {
46+
plansInfo,
47+
account,
48+
organizations: EMPTY_ORGANIZATIONS
49+
};
50+
}
51+
3652
try {
3753
const [plansInfo, organizations] = await Promise.all([
3854
getPlatformPlans(),
@@ -47,15 +63,19 @@ export const load: LayoutLoad = async ({ depends, url, route }) => {
4763
organizations
4864
};
4965
} catch (error) {
50-
if (
51-
error instanceof AppwriteException &&
52-
isEmailVerificationRequiredError(error.type)
53-
) {
54-
const verifyEmailUrl = resolve('/verify-email');
55-
56-
if (url.pathname !== verifyEmailUrl) {
57-
redirect(303, withParams(verifyEmailUrl, url.searchParams));
66+
if (isVerifyEmailRedirectError(error)) {
67+
if (url.pathname !== verifyEmailPath) {
68+
redirect(303, withParams(verifyEmailPath, url.searchParams));
5869
}
70+
71+
// Already on verify-email: do not rethrow; the teams API is blocked until verified.
72+
const plansInfo = await getPlatformPlans();
73+
plansInfoStore.set(plansInfo);
74+
return {
75+
plansInfo,
76+
account,
77+
organizations: EMPTY_ORGANIZATIONS
78+
};
5979
}
6080

6181
throw error;
@@ -77,11 +97,9 @@ export const load: LayoutLoad = async ({ depends, url, route }) => {
7797
redirect(303, withParams(mfaUrl, url.searchParams));
7898
}
7999

80-
if (isEmailVerificationRequiredError(error.type)) {
81-
const verifyEmailUrl = resolve('/verify-email');
82-
83-
if (url.pathname !== verifyEmailUrl) {
84-
redirect(303, withParams(verifyEmailUrl, url.searchParams));
100+
if (isVerifyEmailRedirectError(error)) {
101+
if (url.pathname !== verifyEmailPath) {
102+
redirect(303, withParams(verifyEmailPath, url.searchParams));
85103
}
86104
}
87105

0 commit comments

Comments
 (0)