mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge pull request #3039 from appwrite/feat-signup-email-security
feat: add signup email security controls + ce refactor changes
This commit is contained in:
@@ -25,7 +25,7 @@ jobs:
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('**/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-playwright-
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"name": "@appwrite/console",
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "^12.2.0",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@7789ae4",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
@@ -124,15 +124,15 @@
|
||||
|
||||
"@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="],
|
||||
|
||||
"@appwrite.io/console": ["@appwrite.io/console@12.2.0", "", { "dependencies": { "json-bigint": "1.0.0" } }, "sha512-kv5CS/pWlSkLzG1mPctUr5fMdisZRrkOYStIvAaNPHv+bl/wFCZMMoRoiPGlBpw2NKwTddGFkkQvHq8GbxehNA=="],
|
||||
"@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@7789ae4", { "dependencies": { "json-bigint": "1.0.0" } }],
|
||||
|
||||
"@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="],
|
||||
|
||||
"@appwrite.io/pink-icons-svelte": ["@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", { "peerDependencies": { "svelte": "^4.0.0" } }, "sha512-2HYl/CC2OlfZIR7LzbLXuSPBn0iNkjbnxpaeGCkZ7UNZ/hFeSeeWjDJqTBMdZ8+X95uuZqHx62XPTiE/svuSXQ=="],
|
||||
"@appwrite.io/pink-icons-svelte": ["@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", { "peerDependencies": { "svelte": "^4.0.0" } }],
|
||||
|
||||
"@appwrite.io/pink-legacy": ["@appwrite.io/pink-legacy@1.0.3", "", { "dependencies": { "@appwrite.io/pink-icons": "1.0.0", "the-new-css-reset": "^1.11.2" } }, "sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ=="],
|
||||
|
||||
"@appwrite.io/pink-svelte": ["@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8dcaa17", { "dependencies": { "@appwrite.io/pink-icons-svelte": "2.0.0-RC.1", "@floating-ui/dom": "^1.6.13", "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.86.6", "@tanstack/svelte-virtual": "^3.13.10", "ansicolor": "^2.0.3", "d3": "^7.9.0", "fuse.js": "^7.1.0", "pretty-bytes": "^6.1.1", "shiki": "^1.18.0", "svelte-motion": "^0.12.2", "svelte-sonner": "^0.3.28" }, "peerDependencies": { "svelte": "^4.0.0" } }, "sha512-rw3zXN7/cUciCnhj0FR8M0H5Db+LYYMaKtPxvOAIMxNTBmStzU8kTw6grqIvdtFu9vybIsjKtIwm9QLHpNDBjA=="],
|
||||
"@appwrite.io/pink-svelte": ["@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8dcaa17", { "dependencies": { "@appwrite.io/pink-icons-svelte": "2.0.0-RC.1", "@floating-ui/dom": "^1.6.13", "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.86.6", "@tanstack/svelte-virtual": "^3.13.10", "ansicolor": "^2.0.3", "d3": "^7.9.0", "fuse.js": "^7.1.0", "pretty-bytes": "^6.1.1", "shiki": "^1.18.0", "svelte-motion": "^0.12.2", "svelte-sonner": "^0.3.28" }, "peerDependencies": { "svelte": "^4.0.0" } }],
|
||||
|
||||
"@asamuzakjp/css-color": ["@asamuzakjp/css-color@3.2.0", "", { "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" } }, "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw=="],
|
||||
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "^12.2.0",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@7789ae4",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
|
||||
Generated
-7003
File diff suppressed because it is too large
Load Diff
@@ -272,6 +272,9 @@ export enum Submit {
|
||||
AuthPasswordHistoryUpdate = 'submit_auth_password_history_limit_update',
|
||||
AuthPasswordDictionaryUpdate = 'submit_auth_password_dictionary_update',
|
||||
AuthPersonalDataCheckUpdate = 'submit_auth_personal_data_check_update',
|
||||
AuthAliasedEmailsUpdate = 'submit_auth_aliased_emails_update',
|
||||
AuthDisposableEmailsUpdate = 'submit_auth_disposable_emails_update',
|
||||
AuthFreeEmailsUpdate = 'submit_auth_free_emails_update',
|
||||
AuthSessionAlertsUpdate = 'submit_auth_session_alerts_update',
|
||||
AuthMembershipPrivacyUpdate = 'submit_auth_membership_privacy_update',
|
||||
AuthMockNumbersUpdate = 'submit_auth_mock_numbers_update',
|
||||
|
||||
@@ -70,10 +70,11 @@
|
||||
function getProgressCard() {
|
||||
if (!activeProject || hasOnboardingDismissed(activeProject.$id, $user)) return undefined;
|
||||
|
||||
const { platforms, pingCount } = activeProject;
|
||||
const platforms = page.data.platforms as Models.PlatformList | undefined;
|
||||
const { pingCount } = activeProject;
|
||||
let percentage = 33;
|
||||
|
||||
if (platforms.length > 0 && pingCount === 0) {
|
||||
if ((platforms?.total ?? 0) > 0 && pingCount === 0) {
|
||||
percentage = 66;
|
||||
} else if (pingCount > 0) {
|
||||
percentage = 100;
|
||||
|
||||
@@ -1,48 +1,52 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { type Models, AuthMethod as MethodId } from '@appwrite.io/console';
|
||||
import { type Models, ProjectAuthMethodId } from '@appwrite.io/console';
|
||||
|
||||
export type AuthMethod = {
|
||||
label: string;
|
||||
method: MethodId;
|
||||
method: ProjectAuthMethodId;
|
||||
value: boolean | null;
|
||||
};
|
||||
|
||||
const setAuthMethod = (project: Models.Project): AuthMethod[] => {
|
||||
const authMethods = new Map(
|
||||
project?.authMethods?.map((method) => [method.$id, method.enabled])
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'Email/Password',
|
||||
method: MethodId.Emailpassword,
|
||||
value: project?.authEmailPassword
|
||||
method: ProjectAuthMethodId.Emailpassword,
|
||||
value: authMethods.get(ProjectAuthMethodId.Emailpassword) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Phone',
|
||||
method: MethodId.Phone,
|
||||
value: project?.authPhone
|
||||
method: ProjectAuthMethodId.Phone,
|
||||
value: authMethods.get(ProjectAuthMethodId.Phone) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Magic URL',
|
||||
method: MethodId.Magicurl,
|
||||
value: project?.authUsersAuthMagicURL
|
||||
method: ProjectAuthMethodId.Magicurl,
|
||||
value: authMethods.get(ProjectAuthMethodId.Magicurl) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Email OTP',
|
||||
method: MethodId.Emailotp,
|
||||
value: project?.authEmailOtp
|
||||
method: ProjectAuthMethodId.Emailotp,
|
||||
value: authMethods.get(ProjectAuthMethodId.Emailotp) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Anonymous',
|
||||
method: MethodId.Anonymous,
|
||||
value: project?.authAnonymous
|
||||
method: ProjectAuthMethodId.Anonymous,
|
||||
value: authMethods.get(ProjectAuthMethodId.Anonymous) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Team Invites',
|
||||
method: MethodId.Invites,
|
||||
value: project?.authInvites
|
||||
method: ProjectAuthMethodId.Invites,
|
||||
value: authMethods.get(ProjectAuthMethodId.Invites) ?? null
|
||||
},
|
||||
{
|
||||
label: 'JWT',
|
||||
method: MethodId.Jwt,
|
||||
value: project?.authJWT
|
||||
method: ProjectAuthMethodId.Jwt,
|
||||
value: authMethods.get(ProjectAuthMethodId.Jwt) ?? null
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { ProtocolId, type Models } from '@appwrite.io/console';
|
||||
import { ProjectProtocolId, type Models } from '@appwrite.io/console';
|
||||
|
||||
export type Protocol = {
|
||||
label: string;
|
||||
method: ProtocolId;
|
||||
method: ProjectProtocolId;
|
||||
value: boolean | null;
|
||||
};
|
||||
|
||||
function projectProtocolRows(project: Models.Project | null): Protocol[] {
|
||||
const protocols = new Map(
|
||||
project?.protocols?.map((protocol) => [protocol.$id, protocol.enabled])
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'REST',
|
||||
method: ProtocolId.Rest,
|
||||
value: project?.protocolStatusForRest ?? null
|
||||
method: ProjectProtocolId.Rest,
|
||||
value: protocols.get(ProjectProtocolId.Rest) ?? null
|
||||
},
|
||||
{
|
||||
label: 'GraphQL',
|
||||
method: ProtocolId.Graphql,
|
||||
value: project?.protocolStatusForGraphql ?? null
|
||||
method: ProjectProtocolId.Graphql,
|
||||
value: protocols.get(ProjectProtocolId.Graphql) ?? null
|
||||
},
|
||||
{
|
||||
label: 'WebSocket',
|
||||
method: ProtocolId.Websocket,
|
||||
value: project?.protocolStatusForWebsocket ?? null
|
||||
method: ProjectProtocolId.Websocket,
|
||||
value: protocols.get(ProjectProtocolId.Websocket) ?? null
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,89 +1,91 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { ServiceId, type Models } from '@appwrite.io/console';
|
||||
import { ProjectServiceId, type Models } from '@appwrite.io/console';
|
||||
|
||||
export type Service = {
|
||||
label: string;
|
||||
method: ServiceId;
|
||||
method: ProjectServiceId;
|
||||
value: boolean | null;
|
||||
};
|
||||
|
||||
function projectServiceRows(project: Models.Project | null): Service[] {
|
||||
const services = new Map(project?.services?.map((service) => [service.$id, service.enabled]));
|
||||
|
||||
const rows: Service[] = [
|
||||
{
|
||||
label: 'Account',
|
||||
method: ServiceId.Account,
|
||||
value: project?.serviceStatusForAccount ?? null
|
||||
method: ProjectServiceId.Account,
|
||||
value: services.get(ProjectServiceId.Account) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Avatars',
|
||||
method: ServiceId.Avatars,
|
||||
value: project?.serviceStatusForAvatars ?? null
|
||||
method: ProjectServiceId.Avatars,
|
||||
value: services.get(ProjectServiceId.Avatars) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Databases',
|
||||
method: ServiceId.Databases,
|
||||
value: project?.serviceStatusForDatabases ?? null
|
||||
method: ProjectServiceId.Databases,
|
||||
value: services.get(ProjectServiceId.Databases) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Functions',
|
||||
method: ServiceId.Functions,
|
||||
value: project?.serviceStatusForFunctions ?? null
|
||||
method: ProjectServiceId.Functions,
|
||||
value: services.get(ProjectServiceId.Functions) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Locale',
|
||||
method: ServiceId.Locale,
|
||||
value: project?.serviceStatusForLocale ?? null
|
||||
method: ProjectServiceId.Locale,
|
||||
value: services.get(ProjectServiceId.Locale) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Messaging',
|
||||
method: ServiceId.Messaging,
|
||||
value: project?.serviceStatusForMessaging ?? null
|
||||
method: ProjectServiceId.Messaging,
|
||||
value: services.get(ProjectServiceId.Messaging) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Migrations',
|
||||
method: ServiceId.Migrations,
|
||||
value: project?.serviceStatusForMigrations ?? null
|
||||
method: ProjectServiceId.Migrations,
|
||||
value: services.get(ProjectServiceId.Migrations) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Project',
|
||||
method: ServiceId.Project,
|
||||
value: project?.serviceStatusForProject ?? null
|
||||
method: ProjectServiceId.Project,
|
||||
value: services.get(ProjectServiceId.Project) ?? null
|
||||
},
|
||||
// @todo Re-enable when Proxy is ready for public release.
|
||||
// {
|
||||
// label: 'Proxy',
|
||||
// method: ServiceId.Proxy,
|
||||
// method: ProjectServiceId.Proxy,
|
||||
// value: project?.serviceStatusForProxy ?? null
|
||||
// },
|
||||
{
|
||||
label: 'Sites',
|
||||
method: ServiceId.Sites,
|
||||
value: project?.serviceStatusForSites ?? null
|
||||
method: ProjectServiceId.Sites,
|
||||
value: services.get(ProjectServiceId.Sites) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Storage',
|
||||
method: ServiceId.Storage,
|
||||
value: project?.serviceStatusForStorage ?? null
|
||||
method: ProjectServiceId.Storage,
|
||||
value: services.get(ProjectServiceId.Storage) ?? null
|
||||
},
|
||||
{
|
||||
label: 'TablesDB',
|
||||
method: ServiceId.Tablesdb,
|
||||
value: project?.serviceStatusForTablesdb ?? null
|
||||
method: ProjectServiceId.Tablesdb,
|
||||
value: services.get(ProjectServiceId.Tablesdb) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Teams',
|
||||
method: ServiceId.Teams,
|
||||
value: project?.serviceStatusForTeams ?? null
|
||||
method: ProjectServiceId.Teams,
|
||||
value: services.get(ProjectServiceId.Teams) ?? null
|
||||
},
|
||||
{
|
||||
label: 'Users',
|
||||
method: ServiceId.Users,
|
||||
value: project?.serviceStatusForUsers ?? null
|
||||
method: ProjectServiceId.Users,
|
||||
value: services.get(ProjectServiceId.Users) ?? null
|
||||
}
|
||||
// @todo Re-enable when VCS is ready for public release.
|
||||
// {
|
||||
// label: 'VCS',
|
||||
// method: ServiceId.Vcs,
|
||||
// method: ProjectServiceId.Vcs,
|
||||
// value: project?.serviceStatusForVcs ?? null
|
||||
// }
|
||||
];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import CreateProject from './createProject.svelte';
|
||||
@@ -51,6 +52,7 @@
|
||||
let freePlanAlertDismissed = $state(false);
|
||||
|
||||
let searchQuery: SearchQuery | null = $state(null);
|
||||
let handledCreateProjectQuery = $state(false);
|
||||
const educationProgramId = 'github-student-developer';
|
||||
|
||||
const isEducationProgram = $derived(data.program?.$id === educationProgramId);
|
||||
@@ -84,11 +86,26 @@
|
||||
}
|
||||
|
||||
function handleCreateProject() {
|
||||
if (!$canWriteProjects) return;
|
||||
if (projectCreationDisabled) return;
|
||||
if (isCloud) showCreateProjectCloud = true;
|
||||
else showCreate = true;
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (handledCreateProjectQuery || !page.url.searchParams.has('create-project')) return;
|
||||
|
||||
handledCreateProjectQuery = true;
|
||||
handleCreateProject();
|
||||
|
||||
const url = new URL(page.url);
|
||||
url.searchParams.delete('create-project');
|
||||
void goto(`${url.pathname}${url.search}${url.hash}`, {
|
||||
replaceState: true,
|
||||
noScroll: true,
|
||||
keepFocus: true
|
||||
});
|
||||
});
|
||||
|
||||
function getIconForPlatform(platform: string): ComponentType {
|
||||
switch (platform) {
|
||||
case 'code':
|
||||
@@ -233,13 +250,15 @@
|
||||
offset={data.offset}
|
||||
on:click={handleCreateProject}>
|
||||
{#each data.projects.projects as project}
|
||||
{@const projectPlatforms = project.platforms}
|
||||
{@const platformsTotal = project.platformsTotal}
|
||||
{@const platforms = filterPlatforms(
|
||||
project.platforms.map((platform) => getPlatformInfo(platform.type))
|
||||
projectPlatforms.map((platform) => getPlatformInfo(platform.type))
|
||||
)}
|
||||
<GridItem1
|
||||
href={`${base}/project-${project.region}-${project.$id}/overview/platforms`}>
|
||||
<svelte:fragment slot="eyebrow">
|
||||
{project?.platforms?.length ? project?.platforms?.length : 'No'} apps
|
||||
{platformsTotal ? platformsTotal : 'No'} apps
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
{project.name}
|
||||
@@ -264,10 +283,10 @@
|
||||
</Badge>
|
||||
{/each}
|
||||
|
||||
{#if platforms.length > 3}
|
||||
{#if platformsTotal > 2}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content={`+${platforms.length - 2}`}
|
||||
content={`+${platformsTotal - 2}`}
|
||||
style="width: max-content;" />
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -43,15 +43,28 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
|
||||
]
|
||||
});
|
||||
|
||||
// set `default` if no region!
|
||||
for (const project of activeProjects.projects) {
|
||||
project.region ??= 'default';
|
||||
}
|
||||
const projects = await Promise.all(
|
||||
activeProjects.projects.map(async (project) => {
|
||||
project.region ??= 'default';
|
||||
const platformList = await sdk
|
||||
.forProject(project.region, project.$id)
|
||||
.project.listPlatforms({ queries: [Query.limit(3)] });
|
||||
|
||||
return {
|
||||
...project,
|
||||
platforms: platformList.platforms,
|
||||
platformsTotal: platformList.total
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
limit,
|
||||
offset,
|
||||
search,
|
||||
projects: activeProjects
|
||||
projects: {
|
||||
...activeProjects,
|
||||
projects
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import { get } from 'svelte/store';
|
||||
import { headerAlert } from '$lib/stores/headerAlert';
|
||||
import PaymentFailed from '$lib/components/billing/alerts/paymentFailed.svelte';
|
||||
import { loadAvailableRegions } from '$routes/(console)/regions';
|
||||
import { type Models, Platform } from '@appwrite.io/console';
|
||||
import { Query, type Models, Platform } from '@appwrite.io/console';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { resolve } from '$app/paths';
|
||||
import { generateFingerprintToken } from '$lib/helpers/fingerprint';
|
||||
@@ -19,7 +19,8 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
const { plansInfo, organizations, preferences: prefs } = await parent();
|
||||
depends(Dependencies.PROJECT);
|
||||
|
||||
const project = await sdk.forConsole.projects.get({ projectId: params.project });
|
||||
const projectSdk = sdk.forProject(params.region, params.project).project;
|
||||
const project = await projectSdk.get();
|
||||
if (project.status !== 'active' && project.status !== 'paused') {
|
||||
// project isn't active, redirect back to organizations page
|
||||
redirect(
|
||||
@@ -40,20 +41,24 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
// organization can be null if not in the filtered list!
|
||||
const includedInBasePlans = plansInfo.has(organization?.billingPlanId);
|
||||
|
||||
const [org, rawRegionalConsoleVariables, rolesResult] = await Promise.all([
|
||||
!organization
|
||||
? // TODO: @itznotabug - teams.get with Models.Organization?
|
||||
(sdk.forConsole.teams.get({ teamId: project.teamId }) as Promise<Models.Organization>)
|
||||
: organization,
|
||||
sdk.forConsoleIn(project.region).console.variables(),
|
||||
isCloud
|
||||
? sdk.forConsole.organizations.getScopes({
|
||||
organizationId: project.teamId
|
||||
})
|
||||
: null,
|
||||
|
||||
loadAvailableRegions(project.teamId)
|
||||
]);
|
||||
const [org, rawRegionalConsoleVariables, rolesResult, platformList, keyList] =
|
||||
await Promise.all([
|
||||
!organization
|
||||
? // TODO: @itznotabug - teams.get with Models.Organization?
|
||||
(sdk.forConsole.teams.get({
|
||||
teamId: project.teamId
|
||||
}) as Promise<Models.Organization>)
|
||||
: organization,
|
||||
sdk.forConsoleIn(project.region).console.variables(),
|
||||
isCloud
|
||||
? sdk.forConsole.organizations.getScopes({
|
||||
organizationId: project.teamId
|
||||
})
|
||||
: null,
|
||||
projectSdk.listPlatforms({ queries: [Query.limit(1)] }),
|
||||
projectSdk.listKeys({ queries: [Query.limit(1)] }),
|
||||
loadAvailableRegions(project.teamId)
|
||||
]);
|
||||
|
||||
const regionalConsoleVariables = normalizeConsoleVariables(rawRegionalConsoleVariables);
|
||||
|
||||
@@ -122,6 +127,8 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
|
||||
return {
|
||||
project,
|
||||
platforms: platformList,
|
||||
keys: keyList,
|
||||
organization,
|
||||
regionalConsoleVariables,
|
||||
roles,
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
InputTextarea
|
||||
} from '$lib/elements/forms';
|
||||
import { updateOAuth } from '../updateOAuth';
|
||||
import { OAuthProvider, type Models, type Models as ConsoleModels } from '@appwrite.io/console';
|
||||
import { OAuthProvider, type Models as ConsoleModels } from '@appwrite.io/console';
|
||||
import type { AuthProvider } from '../updateOAuth';
|
||||
import { oAuthProviders } from '$lib/stores/oauth-providers';
|
||||
import {
|
||||
Accordion,
|
||||
@@ -28,7 +29,7 @@
|
||||
const projectId = page.params.project;
|
||||
const region = page.params.region;
|
||||
|
||||
export let provider: Models.AuthProvider;
|
||||
export let provider: AuthProvider;
|
||||
export let show = false;
|
||||
export let parameters: ConsoleModels.ConsoleOAuth2ProviderParameter[] = [];
|
||||
|
||||
@@ -39,7 +40,7 @@
|
||||
let showSecretInput = false;
|
||||
let p8PasteMode: Record<string, boolean> = {};
|
||||
let error: string;
|
||||
let initializedProvider: Models.AuthProvider | null = null;
|
||||
let initializedProvider: AuthProvider | null = null;
|
||||
let initialEnabled = false;
|
||||
let initialAppId: string | null = null;
|
||||
let initialDetailValues: Record<string, string> = {};
|
||||
|
||||
@@ -8,16 +8,29 @@
|
||||
import UpdateSessionsLimit from './updateSessionsLimit.svelte';
|
||||
import PasswordPolicies from './passwordPolicies.svelte';
|
||||
import SessionSecurity from './sessionSecurity.svelte';
|
||||
import UpdateSignupEmailSecurity from './updateSignupEmailSecurity.svelte';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<UpdateUsersLimit project={data.project} />
|
||||
<UpdateSessionLength project={data.project} />
|
||||
<UpdateSessionsLimit project={data.project} />
|
||||
<PasswordPolicies project={data.project} />
|
||||
<SessionSecurity project={data.project} />
|
||||
<UpdateUsersLimit project={data.project} policy={data.userLimitPolicy} />
|
||||
<UpdateSessionLength project={data.project} policy={data.sessionDurationPolicy} />
|
||||
<UpdateSessionsLimit project={data.project} policy={data.sessionLimitPolicy} />
|
||||
<PasswordPolicies
|
||||
project={data.project}
|
||||
dictionaryPolicy={data.passwordDictionaryPolicy}
|
||||
historyPolicy={data.passwordHistoryPolicy}
|
||||
personalDataPolicy={data.passwordPersonalDataPolicy} />
|
||||
<UpdateSignupEmailSecurity
|
||||
project={data.project}
|
||||
denyAliasedEmailPolicy={data.denyAliasedEmailPolicy}
|
||||
denyDisposableEmailPolicy={data.denyDisposableEmailPolicy}
|
||||
denyFreeEmailPolicy={data.denyFreeEmailPolicy} />
|
||||
<SessionSecurity
|
||||
project={data.project}
|
||||
sessionAlertPolicy={data.sessionAlertPolicy}
|
||||
sessionInvalidationPolicy={data.sessionInvalidationPolicy} />
|
||||
<UpdateMockNumbers project={data.project} />
|
||||
<UpdateMembershipPrivacy project={data.project} />
|
||||
<UpdateMembershipPrivacy project={data.project} policy={data.membershipPrivacyPolicy} />
|
||||
</Container>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { ProjectPolicyId, type Models } from '@appwrite.io/console';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
type EnabledPolicy = {
|
||||
$id: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
const ProjectEmailPolicyId = {
|
||||
DenyAliasedEmail: 'deny-aliased-email',
|
||||
DenyDisposableEmail: 'deny-disposable-email',
|
||||
DenyFreeEmail: 'deny-free-email'
|
||||
} as const;
|
||||
|
||||
type ProjectPolicy = Models.PolicyList['policies'][number] | EnabledPolicy;
|
||||
type EmailPolicyId = (typeof ProjectEmailPolicyId)[keyof typeof ProjectEmailPolicyId];
|
||||
|
||||
const getDefaultEnabledPolicy = (policyId: EmailPolicyId): EnabledPolicy => ({
|
||||
$id: policyId,
|
||||
enabled: false
|
||||
});
|
||||
|
||||
export const load: PageLoad = async ({ depends, params }) => {
|
||||
depends(Dependencies.PROJECT);
|
||||
|
||||
const { policies } = await sdk.forProject(params.region, params.project).project.listPolicies();
|
||||
const policiesById = Object.fromEntries(
|
||||
(policies as ProjectPolicy[]).map((policy) => [policy.$id, policy])
|
||||
) as Partial<Record<ProjectPolicyId | EmailPolicyId, ProjectPolicy>>;
|
||||
|
||||
return {
|
||||
membershipPrivacyPolicy: policiesById[
|
||||
ProjectPolicyId.Membershipprivacy
|
||||
] as Models.PolicyMembershipPrivacy,
|
||||
passwordDictionaryPolicy: policiesById[
|
||||
ProjectPolicyId.Passworddictionary
|
||||
] as Models.PolicyPasswordDictionary,
|
||||
passwordHistoryPolicy: policiesById[
|
||||
ProjectPolicyId.Passwordhistory
|
||||
] as Models.PolicyPasswordHistory,
|
||||
passwordPersonalDataPolicy: policiesById[
|
||||
ProjectPolicyId.Passwordpersonaldata
|
||||
] as Models.PolicyPasswordPersonalData,
|
||||
sessionAlertPolicy: policiesById[ProjectPolicyId.Sessionalert] as Models.PolicySessionAlert,
|
||||
sessionDurationPolicy: policiesById[
|
||||
ProjectPolicyId.Sessionduration
|
||||
] as Models.PolicySessionDuration,
|
||||
sessionInvalidationPolicy: policiesById[
|
||||
ProjectPolicyId.Sessioninvalidation
|
||||
] as Models.PolicySessionInvalidation,
|
||||
sessionLimitPolicy: policiesById[ProjectPolicyId.Sessionlimit] as Models.PolicySessionLimit,
|
||||
userLimitPolicy: policiesById[ProjectPolicyId.Userlimit] as Models.PolicyUserLimit,
|
||||
denyAliasedEmailPolicy:
|
||||
(policiesById[ProjectEmailPolicyId.DenyAliasedEmail] as EnabledPolicy) ??
|
||||
getDefaultEnabledPolicy(ProjectEmailPolicyId.DenyAliasedEmail),
|
||||
denyDisposableEmailPolicy:
|
||||
(policiesById[ProjectEmailPolicyId.DenyDisposableEmail] as EnabledPolicy) ??
|
||||
getDefaultEnabledPolicy(ProjectEmailPolicyId.DenyDisposableEmail),
|
||||
denyFreeEmailPolicy:
|
||||
(policiesById[ProjectEmailPolicyId.DenyFreeEmail] as EnabledPolicy) ??
|
||||
getDefaultEnabledPolicy(ProjectEmailPolicyId.DenyFreeEmail)
|
||||
};
|
||||
};
|
||||
+14
-10
@@ -11,9 +11,15 @@
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let {
|
||||
project
|
||||
project,
|
||||
dictionaryPolicy,
|
||||
historyPolicy,
|
||||
personalDataPolicy
|
||||
}: {
|
||||
project: Models.Project;
|
||||
dictionaryPolicy: Models.PolicyPasswordDictionary;
|
||||
historyPolicy: Models.PolicyPasswordHistory;
|
||||
personalDataPolicy: Models.PolicyPasswordPersonalData;
|
||||
} = $props();
|
||||
|
||||
let lastValidLimit = $state(5);
|
||||
@@ -24,15 +30,15 @@
|
||||
|
||||
onMount(() => {
|
||||
// update initial states here in onMount.
|
||||
const historyValue = project.authPasswordHistory;
|
||||
const historyValue = historyPolicy.total;
|
||||
if (historyValue && historyValue > 0) {
|
||||
passwordHistory = historyValue;
|
||||
lastValidLimit = historyValue;
|
||||
}
|
||||
|
||||
passwordHistoryEnabled = (historyValue ?? 0) !== 0;
|
||||
passwordDictionary = project.authPasswordDictionary ?? false;
|
||||
authPersonalDataCheck = project.authPersonalDataCheck ?? false;
|
||||
passwordDictionary = dictionaryPolicy.enabled;
|
||||
authPersonalDataCheck = personalDataPolicy.enabled;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
@@ -43,13 +49,11 @@
|
||||
});
|
||||
|
||||
const hasChanges = $derived.by(() => {
|
||||
const dictChanged = passwordDictionary !== (project.authPasswordDictionary ?? false);
|
||||
const dataCheckChanged = authPersonalDataCheck !== (project.authPersonalDataCheck ?? false);
|
||||
const historyChanged =
|
||||
passwordHistoryEnabled !== ((project.authPasswordHistory ?? 0) !== 0);
|
||||
const dictChanged = passwordDictionary !== dictionaryPolicy.enabled;
|
||||
const dataCheckChanged = authPersonalDataCheck !== personalDataPolicy.enabled;
|
||||
const historyChanged = passwordHistoryEnabled !== (historyPolicy.total !== 0);
|
||||
const limitChanged =
|
||||
passwordHistoryEnabled &&
|
||||
Number(passwordHistory) !== (project.authPasswordHistory ?? 0);
|
||||
passwordHistoryEnabled && Number(passwordHistory) !== historyPolicy.total;
|
||||
|
||||
return historyChanged || dictChanged || dataCheckChanged || limitChanged;
|
||||
});
|
||||
|
||||
+13
-6
@@ -10,20 +10,27 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let { project }: { project: Models.Project } = $props();
|
||||
let {
|
||||
project,
|
||||
sessionAlertPolicy,
|
||||
sessionInvalidationPolicy
|
||||
}: {
|
||||
project: Models.Project;
|
||||
sessionAlertPolicy: Models.PolicySessionAlert;
|
||||
sessionInvalidationPolicy: Models.PolicySessionInvalidation;
|
||||
} = $props();
|
||||
|
||||
let authSessionAlerts = $state(false);
|
||||
let sessionInvalidation = $state(false);
|
||||
|
||||
onMount(() => {
|
||||
authSessionAlerts = project?.authSessionAlerts ?? false;
|
||||
sessionInvalidation = project?.authInvalidateSessions ?? false;
|
||||
authSessionAlerts = sessionAlertPolicy.enabled;
|
||||
sessionInvalidation = sessionInvalidationPolicy.enabled;
|
||||
});
|
||||
|
||||
const hasChanges = $derived.by(() => {
|
||||
const alertsChanged = authSessionAlerts !== (project?.authSessionAlerts ?? false);
|
||||
const invalidationChanged =
|
||||
sessionInvalidation !== (project?.authInvalidateSessions ?? false);
|
||||
const alertsChanged = authSessionAlerts !== sessionAlertPolicy.enabled;
|
||||
const invalidationChanged = sessionInvalidation !== sessionInvalidationPolicy.enabled;
|
||||
return alertsChanged || invalidationChanged;
|
||||
});
|
||||
|
||||
|
||||
+13
-11
@@ -10,23 +10,25 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
const {
|
||||
project
|
||||
project,
|
||||
policy
|
||||
}: {
|
||||
project: Models.Project;
|
||||
policy: Models.PolicyMembershipPrivacy;
|
||||
} = $props();
|
||||
|
||||
let authMembershipsMfa = $state(project?.authMembershipsMfa ?? true);
|
||||
let authMembershipsUserId = $state(project?.authMembershipsUserId ?? true);
|
||||
let authMembershipsUserName = $state(project?.authMembershipsUserName ?? true);
|
||||
let authMembershipsUserEmail = $state(project?.authMembershipsUserEmail ?? true);
|
||||
let authMembershipsUserPhone = $state(project?.authMembershipsUserPhone ?? true);
|
||||
let authMembershipsMfa = $state(policy.userMFA);
|
||||
let authMembershipsUserId = $state(policy.userId);
|
||||
let authMembershipsUserName = $state(policy.userName);
|
||||
let authMembershipsUserEmail = $state(policy.userEmail);
|
||||
let authMembershipsUserPhone = $state(policy.userPhone);
|
||||
|
||||
const isSubmitDisabled = $derived(
|
||||
authMembershipsUserId === (project?.authMembershipsUserId ?? true) &&
|
||||
authMembershipsUserName === (project?.authMembershipsUserName ?? true) &&
|
||||
authMembershipsUserEmail === (project?.authMembershipsUserEmail ?? true) &&
|
||||
authMembershipsUserPhone === (project?.authMembershipsUserPhone ?? true) &&
|
||||
authMembershipsMfa === (project?.authMembershipsMfa ?? true)
|
||||
authMembershipsUserId === policy.userId &&
|
||||
authMembershipsUserName === policy.userName &&
|
||||
authMembershipsUserEmail === policy.userEmail &&
|
||||
authMembershipsUserPhone === policy.userPhone &&
|
||||
authMembershipsMfa === policy.userMFA
|
||||
);
|
||||
|
||||
async function updateMembershipsPrivacy() {
|
||||
|
||||
+26
-7
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { CardGrid, PaginationInline } from '$lib/components';
|
||||
import Confirm from '$lib/components/confirm.svelte';
|
||||
import { InputPhone, InputOTP, Button } from '$lib/elements/forms';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -13,7 +13,7 @@
|
||||
import EmptyCardImageCloud from '$lib/components/emptyCardImageCloud.svelte';
|
||||
import { app } from '$lib/stores/app';
|
||||
import Empty from '$lib/components/empty.svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Query, type Models } from '@appwrite.io/console';
|
||||
import { Icon, Input, Layout, Link, Spinner, Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus, IconRefresh, IconTrash } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
let savedNumbers: MockNumberForm[] = $state([]);
|
||||
let draftNumbers: MockNumberForm[] = $state([]);
|
||||
let isLoadingMockNumbers = $state(false);
|
||||
let mockNumbersTotal = $state(0);
|
||||
let mockNumbersOffset = $state(0);
|
||||
const mockNumbersLimit = 10;
|
||||
let lastProjectId: string | null = $state(null);
|
||||
let pendingRow: string | null = $state(null);
|
||||
let showDeleteConfirm = $state(false);
|
||||
@@ -79,7 +82,16 @@
|
||||
}
|
||||
const response = await sdk
|
||||
.forProject(project.region, project.$id)
|
||||
.project.listMockPhones({});
|
||||
.project.listMockPhones({
|
||||
queries: [Query.limit(mockNumbersLimit), Query.offset(mockNumbersOffset)]
|
||||
});
|
||||
if (response.total > 0 && response.mockNumbers.length === 0 && mockNumbersOffset > 0) {
|
||||
mockNumbersOffset =
|
||||
Math.floor((response.total - 1) / mockNumbersLimit) * mockNumbersLimit;
|
||||
await loadMockNumbers();
|
||||
return;
|
||||
}
|
||||
mockNumbersTotal = response.total;
|
||||
savedNumbers = getMockNumberRows(response.mockNumbers ?? []);
|
||||
} catch (error) {
|
||||
addNotification({ type: 'error', message: error.message });
|
||||
@@ -381,7 +393,7 @@
|
||||
id={`draft-key-${index}`}
|
||||
bind:value={number.number}
|
||||
placeholder="Enter phone number"
|
||||
label={savedNumbers.length === 0 && index === 0
|
||||
label={mockNumbersTotal === 0 && index === 0
|
||||
? 'Phone number'
|
||||
: undefined}
|
||||
minlength={9}
|
||||
@@ -404,7 +416,7 @@
|
||||
id={`draft-value-${index}`}
|
||||
bind:value={number.otp}
|
||||
placeholder="Enter value"
|
||||
label={savedNumbers.length === 0 && index === 0
|
||||
label={mockNumbersTotal === 0 && index === 0
|
||||
? 'Verification code'
|
||||
: undefined}
|
||||
maxlength={6}
|
||||
@@ -442,17 +454,24 @@
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
{/each}
|
||||
{#if savedNumbers.length + draftNumbers.length < 10}
|
||||
{#if mockNumbersTotal + draftNumbers.length < 10}
|
||||
<div>
|
||||
<Button
|
||||
secondary
|
||||
on:click={addPhoneNumber}
|
||||
disabled={savedNumbers.length + draftNumbers.length >= 10}>
|
||||
disabled={mockNumbersTotal + draftNumbers.length >= 10}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add number
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if mockNumbersTotal > mockNumbersLimit}
|
||||
<PaginationInline
|
||||
bind:offset={mockNumbersOffset}
|
||||
limit={mockNumbersLimit}
|
||||
total={mockNumbersTotal}
|
||||
on:change={loadMockNumbers} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Empty on:click={addPhoneNumber}>Add a number</Empty>
|
||||
{/if}
|
||||
|
||||
+5
-3
@@ -11,12 +11,14 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
let {
|
||||
project
|
||||
project,
|
||||
policy
|
||||
}: {
|
||||
project: Models.Project;
|
||||
policy: Models.PolicySessionDuration;
|
||||
} = $props();
|
||||
|
||||
const { value, unit, baseValue, units } = $derived(createTimeUnitPair(project?.authDuration));
|
||||
const { value, unit, baseValue, units } = $derived(createTimeUnitPair(policy.duration));
|
||||
const options = $derived(units.map((v) => ({ label: v.name, value: v.name })));
|
||||
|
||||
async function updateSessionLength() {
|
||||
@@ -51,7 +53,7 @@
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={$baseValue === project.authDuration} on:click={updateSessionLength}>
|
||||
<Button disabled={$baseValue === policy.duration} on:click={updateSessionLength}>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
|
||||
+5
-3
@@ -10,12 +10,14 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
let {
|
||||
project
|
||||
project,
|
||||
policy
|
||||
}: {
|
||||
project: Models.Project;
|
||||
policy: Models.PolicySessionLimit;
|
||||
} = $props();
|
||||
|
||||
let maxSessions = $state(project?.authSessionsLimit);
|
||||
let maxSessions = $state(policy.total);
|
||||
|
||||
async function updateSessionsLimit() {
|
||||
try {
|
||||
@@ -54,7 +56,7 @@
|
||||
bind:value={maxSessions} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={maxSessions === project?.authSessionsLimit} submit>Update</Button>
|
||||
<Button disabled={maxSessions === policy.total} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputSwitch } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
type EnabledPolicy = {
|
||||
$id: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
let {
|
||||
project,
|
||||
denyAliasedEmailPolicy,
|
||||
denyDisposableEmailPolicy,
|
||||
denyFreeEmailPolicy
|
||||
}: {
|
||||
project: Models.Project;
|
||||
denyAliasedEmailPolicy: EnabledPolicy;
|
||||
denyDisposableEmailPolicy: EnabledPolicy;
|
||||
denyFreeEmailPolicy: EnabledPolicy;
|
||||
} = $props();
|
||||
|
||||
const getInitialAliasedEmails = () => denyAliasedEmailPolicy.enabled;
|
||||
const getInitialDisposableEmails = () => denyDisposableEmailPolicy.enabled;
|
||||
const getInitialFreeEmails = () => denyFreeEmailPolicy.enabled;
|
||||
|
||||
let savedAliasedEmails = $state(getInitialAliasedEmails());
|
||||
let savedDisposableEmails = $state(getInitialDisposableEmails());
|
||||
let savedFreeEmails = $state(getInitialFreeEmails());
|
||||
|
||||
let authAliasedEmails = $state(getInitialAliasedEmails());
|
||||
let authDisposableEmails = $state(getInitialDisposableEmails());
|
||||
let authFreeEmails = $state(getInitialFreeEmails());
|
||||
|
||||
const hasChanges = $derived.by(() => {
|
||||
const aliasedChanged = authAliasedEmails !== savedAliasedEmails;
|
||||
const disposableChanged = authDisposableEmails !== savedDisposableEmails;
|
||||
const freeChanged = authFreeEmails !== savedFreeEmails;
|
||||
|
||||
return aliasedChanged || disposableChanged || freeChanged;
|
||||
});
|
||||
|
||||
async function updateSignupEmailSecurity() {
|
||||
let currentSubmit = Submit.AuthAliasedEmailsUpdate;
|
||||
let hasAppliedServerChange = false;
|
||||
|
||||
try {
|
||||
const projectSdk = sdk.forProject(project.region, project.$id).project;
|
||||
|
||||
if (authAliasedEmails !== savedAliasedEmails) {
|
||||
currentSubmit = Submit.AuthAliasedEmailsUpdate;
|
||||
await projectSdk.updateDenyAliasedEmailPolicy({
|
||||
enabled: authAliasedEmails
|
||||
});
|
||||
hasAppliedServerChange = true;
|
||||
trackEvent(Submit.AuthAliasedEmailsUpdate);
|
||||
}
|
||||
|
||||
if (authDisposableEmails !== savedDisposableEmails) {
|
||||
currentSubmit = Submit.AuthDisposableEmailsUpdate;
|
||||
await projectSdk.updateDenyDisposableEmailPolicy({
|
||||
enabled: authDisposableEmails
|
||||
});
|
||||
hasAppliedServerChange = true;
|
||||
trackEvent(Submit.AuthDisposableEmailsUpdate);
|
||||
}
|
||||
|
||||
if (authFreeEmails !== savedFreeEmails) {
|
||||
currentSubmit = Submit.AuthFreeEmailsUpdate;
|
||||
await projectSdk.updateDenyFreeEmailPolicy({
|
||||
enabled: authFreeEmails
|
||||
});
|
||||
hasAppliedServerChange = true;
|
||||
trackEvent(Submit.AuthFreeEmailsUpdate);
|
||||
}
|
||||
|
||||
savedAliasedEmails = authAliasedEmails;
|
||||
savedDisposableEmails = authDisposableEmails;
|
||||
savedFreeEmails = authFreeEmails;
|
||||
await invalidate(Dependencies.PROJECT);
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Updated signup email security settings.'
|
||||
});
|
||||
} catch (error) {
|
||||
if (hasAppliedServerChange) {
|
||||
await invalidate(Dependencies.PROJECT);
|
||||
}
|
||||
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, currentSubmit);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updateSignupEmailSecurity}>
|
||||
<CardGrid gap="xxl">
|
||||
<svelte:fragment slot="title">Email policies</svelte:fragment>
|
||||
Control which email addresses can be used for user creation and email updates. This does not affect
|
||||
session creation (sign in)
|
||||
<svelte:fragment slot="aside">
|
||||
<InputSwitch bind:value={authFreeEmails} id="authFreeEmails" label="Deny free emails">
|
||||
<svelte:fragment slot="description">
|
||||
<Layout.Stack gap="s">
|
||||
<Typography.Text
|
||||
>Block email addresses of free email providers. For example: <Typography.Code
|
||||
>user@gmail.com</Typography.Code>
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</InputSwitch>
|
||||
|
||||
<InputSwitch
|
||||
bind:value={authAliasedEmails}
|
||||
id="authAliasedEmails"
|
||||
label="Deny aliased emails">
|
||||
<svelte:fragment slot="description">
|
||||
<Layout.Stack gap="s">
|
||||
<Typography.Text>
|
||||
Block emails with aliases, tags, subaddresses, or any other
|
||||
provider-specific email variations. For example: <Typography.Code
|
||||
>user+folder1@gmail.com</Typography.Code>
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</InputSwitch>
|
||||
|
||||
<InputSwitch
|
||||
bind:value={authDisposableEmails}
|
||||
id="authDisposableEmails"
|
||||
label="Deny disposable emails">
|
||||
<svelte:fragment slot="description">
|
||||
<Layout.Stack gap="s">
|
||||
<Typography.Text
|
||||
>Block temporary and disposable email providers. For example: <Typography.Code
|
||||
>alex9734@mailinator.com</Typography.Code>
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</InputSwitch>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={!hasChanges} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
+6
-7
@@ -11,22 +11,21 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
let {
|
||||
project
|
||||
project,
|
||||
policy
|
||||
}: {
|
||||
project: Models.Project;
|
||||
policy: Models.PolicyUserLimit;
|
||||
} = $props();
|
||||
|
||||
let maxUsersInputField: HTMLInputElement | null = $state(null);
|
||||
|
||||
let value = $state(project?.authLimit !== 0 ? 'limited' : 'unlimited');
|
||||
let newLimit = $state(project?.authLimit !== 0 ? project?.authLimit : 100);
|
||||
let value = $state(policy.total !== 0 ? 'limited' : 'unlimited');
|
||||
let newLimit = $state(policy.total !== 0 ? policy.total : 100);
|
||||
|
||||
const isLimited = $derived(value === 'limited');
|
||||
const btnDisabled = $derived.by(() => {
|
||||
return (
|
||||
(!isLimited && project?.authLimit === 0) ||
|
||||
(isLimited && project?.authLimit === newLimit)
|
||||
);
|
||||
return (!isLimited && policy.total === 0) || (isLimited && policy.total === newLimit);
|
||||
});
|
||||
|
||||
async function updateLimit() {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { oAuthProviders } from '$lib/stores/oauth-providers';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { AuthMethod as MethodId, type Models } from '@appwrite.io/console';
|
||||
import { ProjectAuthMethodId } from '@appwrite.io/console';
|
||||
import { base } from '$app/paths';
|
||||
import {
|
||||
Avatar,
|
||||
@@ -24,6 +24,7 @@
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { PageProps } from './$types';
|
||||
import type { AuthProvider } from '../updateOAuth';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
@@ -34,11 +35,11 @@
|
||||
const consoleParamsMap = $derived(data.consoleParamsMap);
|
||||
|
||||
let showProvider = $state(false);
|
||||
let selectedProvider: Models.AuthProvider | null = $state(null);
|
||||
let selectedProvider: AuthProvider | null = $state(null);
|
||||
let isUpdatingAllAuthMethods = $state(false);
|
||||
let showUpdateAuthMethodsDialog = $state(false);
|
||||
let updateAuthMethodsEnabledMode = $state<boolean | null>(null);
|
||||
let apiAuthMethodUpdates = new SvelteSet<MethodId>();
|
||||
let apiAuthMethodUpdates = new SvelteSet<ProjectAuthMethodId>();
|
||||
|
||||
const isAnyAuthMethodUpdating = $derived(apiAuthMethodUpdates.size > 0);
|
||||
const isAnyUpdateInProgress = $derived(isUpdatingAllAuthMethods || isAnyAuthMethodUpdating);
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { oAuthProviders } from '$lib/stores/oauth-providers';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models, Models as ConsoleModels } from '@appwrite.io/console';
|
||||
import type { Models as ConsoleModels } from '@appwrite.io/console';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
/** Extract the appId equivalent from a typed OAuth2 provider model. */
|
||||
function extractAppId(p: Record<string, unknown>): string {
|
||||
return (p['clientId'] ??
|
||||
p['appId'] ??
|
||||
p['oauthClientId'] ??
|
||||
p['oauth2ClientId'] ??
|
||||
p['applicationId'] ??
|
||||
p['serviceId'] ??
|
||||
p['apiKey'] ??
|
||||
p['publicKey'] ??
|
||||
p['appKey'] ??
|
||||
p['keyString'] ??
|
||||
p['customerKey'] ??
|
||||
p['key'] ??
|
||||
'') as string;
|
||||
}
|
||||
import type { AuthProvider } from '../updateOAuth';
|
||||
|
||||
export const load: PageLoad = async ({ depends, params }) => {
|
||||
depends(Dependencies.PROJECT);
|
||||
@@ -34,19 +18,13 @@ export const load: PageLoad = async ({ depends, params }) => {
|
||||
consoleParamsMap.set(cp.$id, cp.parameters ?? []);
|
||||
}
|
||||
|
||||
const oauthProviders: Models.AuthProvider[] = providerList.providers
|
||||
const oauthProviders: AuthProvider[] = providerList.providers
|
||||
.filter((p) => p.$id !== 'mock')
|
||||
.map((p) => {
|
||||
const raw = p as unknown as Record<string, unknown>;
|
||||
return {
|
||||
...raw,
|
||||
key: p.$id,
|
||||
name: oAuthProviders[p.$id]?.name ?? p.$id,
|
||||
appId: extractAppId(raw),
|
||||
secret: '',
|
||||
enabled: p.enabled
|
||||
};
|
||||
});
|
||||
.map((p) => ({
|
||||
...p,
|
||||
key: p.$id,
|
||||
name: oAuthProviders[p.$id]?.name ?? p.$id
|
||||
}));
|
||||
|
||||
return { oauthProviders, consoleParamsMap };
|
||||
};
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script module lang="ts">
|
||||
import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ProjectEmailTemplateLocale,
|
||||
ProjectEmailTemplateId,
|
||||
type Models
|
||||
} from '@appwrite.io/console';
|
||||
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -8,8 +12,8 @@
|
||||
export async function loadEmailTemplate(
|
||||
region: string,
|
||||
projectId: string,
|
||||
type: EmailTemplateType,
|
||||
locale: EmailTemplateLocale = EmailTemplateLocale.En
|
||||
type: ProjectEmailTemplateId,
|
||||
locale: ProjectEmailTemplateLocale = ProjectEmailTemplateLocale.En
|
||||
): Promise<EmailTemplateForm> {
|
||||
try {
|
||||
const template = await sdk.forProject(region, projectId).project.getEmailTemplate({
|
||||
@@ -27,8 +31,8 @@
|
||||
|
||||
function normalizeEmailTemplate(
|
||||
template: Models.EmailTemplate,
|
||||
type: EmailTemplateType,
|
||||
locale: EmailTemplateLocale = EmailTemplateLocale.En
|
||||
type: ProjectEmailTemplateId,
|
||||
locale: ProjectEmailTemplateLocale = ProjectEmailTemplateLocale.En
|
||||
): EmailTemplateForm {
|
||||
return {
|
||||
...template,
|
||||
@@ -58,9 +62,9 @@
|
||||
let isTemplateLoading = $state(false);
|
||||
let openStates = $state(Object.fromEntries(templates.map(({ key }) => [key, false])));
|
||||
|
||||
loadTemplateFor(EmailTemplateType.Verification);
|
||||
loadTemplateFor(ProjectEmailTemplateId.Verification);
|
||||
|
||||
async function loadTemplateFor(type: EmailTemplateType) {
|
||||
async function loadTemplateFor(type: ProjectEmailTemplateId) {
|
||||
// return, already loaded!
|
||||
if (templateType === type) return;
|
||||
|
||||
@@ -71,14 +75,14 @@
|
||||
page.params.region,
|
||||
page.params.project,
|
||||
type,
|
||||
EmailTemplateLocale.En
|
||||
ProjectEmailTemplateLocale.En
|
||||
);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
|
||||
isTemplateLoading = false;
|
||||
}
|
||||
|
||||
function toggleAccordion(type: EmailTemplateType) {
|
||||
function toggleAccordion(type: ProjectEmailTemplateId) {
|
||||
for (const key in openStates) {
|
||||
openStates[key] = false;
|
||||
}
|
||||
|
||||
+11
-4
@@ -7,14 +7,18 @@
|
||||
import { Id } from '$lib/components';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Layout, Card } from '@appwrite.io/pink-svelte';
|
||||
import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ProjectEmailTemplateLocale,
|
||||
ProjectEmailTemplateId,
|
||||
type Models
|
||||
} from '@appwrite.io/console';
|
||||
|
||||
export let loading = false;
|
||||
export let project: Models.Project;
|
||||
export let localeCodes: Models.LocaleCode[];
|
||||
|
||||
let isUpdating = false;
|
||||
let locale = EmailTemplateLocale.En;
|
||||
let locale = ProjectEmailTemplateLocale.En;
|
||||
|
||||
async function onLocaleChange() {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -24,12 +28,15 @@
|
||||
const template = await loadEmailTemplate(
|
||||
project.region,
|
||||
project.$id,
|
||||
EmailTemplateType.MfaChallenge,
|
||||
ProjectEmailTemplateId.MfaChallenge,
|
||||
locale
|
||||
);
|
||||
emailTemplate.set(template);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.MfaChallenge });
|
||||
trackEvent(Submit.EmailChangeLocale, {
|
||||
locale,
|
||||
type: ProjectEmailTemplateId.MfaChallenge
|
||||
});
|
||||
} catch (error) {
|
||||
trackError(error, Submit.EmailChangeLocale);
|
||||
addNotification({
|
||||
|
||||
+11
-4
@@ -7,14 +7,18 @@
|
||||
import { Id } from '$lib/components';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Layout, Card } from '@appwrite.io/pink-svelte';
|
||||
import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ProjectEmailTemplateLocale,
|
||||
ProjectEmailTemplateId,
|
||||
type Models
|
||||
} from '@appwrite.io/console';
|
||||
|
||||
export let loading = false;
|
||||
export let project: Models.Project;
|
||||
export let localeCodes: Models.LocaleCode[];
|
||||
|
||||
let isUpdating = false;
|
||||
let locale = EmailTemplateLocale.En;
|
||||
let locale = ProjectEmailTemplateLocale.En;
|
||||
|
||||
async function onLocaleChange() {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -24,12 +28,15 @@
|
||||
const template = await loadEmailTemplate(
|
||||
project.region,
|
||||
project.$id,
|
||||
EmailTemplateType.Invitation,
|
||||
ProjectEmailTemplateId.Invitation,
|
||||
locale
|
||||
);
|
||||
emailTemplate.set(template);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Invitation });
|
||||
trackEvent(Submit.EmailChangeLocale, {
|
||||
locale,
|
||||
type: ProjectEmailTemplateId.Invitation
|
||||
});
|
||||
} catch (error) {
|
||||
trackError(error, Submit.EmailChangeLocale);
|
||||
addNotification({
|
||||
|
||||
+11
-4
@@ -7,14 +7,18 @@
|
||||
import { Id } from '$lib/components';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Layout, Card } from '@appwrite.io/pink-svelte';
|
||||
import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ProjectEmailTemplateLocale,
|
||||
ProjectEmailTemplateId,
|
||||
type Models
|
||||
} from '@appwrite.io/console';
|
||||
|
||||
export let loading = false;
|
||||
export let project: Models.Project;
|
||||
export let localeCodes: Models.LocaleCode[];
|
||||
|
||||
let isUpdating = false;
|
||||
let locale = EmailTemplateLocale.En;
|
||||
let locale = ProjectEmailTemplateLocale.En;
|
||||
|
||||
async function onLocaleChange() {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -24,12 +28,15 @@
|
||||
const template = await loadEmailTemplate(
|
||||
project.region,
|
||||
project.$id,
|
||||
EmailTemplateType.MagicSession,
|
||||
ProjectEmailTemplateId.MagicSession,
|
||||
locale
|
||||
);
|
||||
emailTemplate.set(template);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.MagicSession });
|
||||
trackEvent(Submit.EmailChangeLocale, {
|
||||
locale,
|
||||
type: ProjectEmailTemplateId.MagicSession
|
||||
});
|
||||
} catch (error) {
|
||||
trackError(error, Submit.EmailChangeLocale);
|
||||
addNotification({
|
||||
|
||||
+11
-4
@@ -7,14 +7,18 @@
|
||||
import { Id } from '$lib/components';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Layout, Card } from '@appwrite.io/pink-svelte';
|
||||
import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ProjectEmailTemplateLocale,
|
||||
ProjectEmailTemplateId,
|
||||
type Models
|
||||
} from '@appwrite.io/console';
|
||||
|
||||
export let loading = false;
|
||||
export let project: Models.Project;
|
||||
export let localeCodes: Models.LocaleCode[];
|
||||
|
||||
let isUpdating = false;
|
||||
let locale = EmailTemplateLocale.En;
|
||||
let locale = ProjectEmailTemplateLocale.En;
|
||||
|
||||
async function onLocaleChange() {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -24,12 +28,15 @@
|
||||
const template = await loadEmailTemplate(
|
||||
project.region,
|
||||
project.$id,
|
||||
EmailTemplateType.OtpSession,
|
||||
ProjectEmailTemplateId.OtpSession,
|
||||
locale
|
||||
);
|
||||
emailTemplate.set(template);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.OtpSession });
|
||||
trackEvent(Submit.EmailChangeLocale, {
|
||||
locale,
|
||||
type: ProjectEmailTemplateId.OtpSession
|
||||
});
|
||||
} catch (error) {
|
||||
trackError(error, Submit.EmailChangeLocale);
|
||||
addNotification({
|
||||
|
||||
+8
-4
@@ -7,14 +7,18 @@
|
||||
import { Id } from '$lib/components';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Layout, Card } from '@appwrite.io/pink-svelte';
|
||||
import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ProjectEmailTemplateLocale,
|
||||
ProjectEmailTemplateId,
|
||||
type Models
|
||||
} from '@appwrite.io/console';
|
||||
|
||||
export let loading = false;
|
||||
export let project: Models.Project;
|
||||
export let localeCodes: Models.LocaleCode[];
|
||||
|
||||
let isUpdating = false;
|
||||
let locale = EmailTemplateLocale.En;
|
||||
let locale = ProjectEmailTemplateLocale.En;
|
||||
|
||||
async function onLocaleChange() {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -24,12 +28,12 @@
|
||||
const template = await loadEmailTemplate(
|
||||
project.region,
|
||||
project.$id,
|
||||
EmailTemplateType.Recovery,
|
||||
ProjectEmailTemplateId.Recovery,
|
||||
locale
|
||||
);
|
||||
emailTemplate.set(template);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Recovery });
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: ProjectEmailTemplateId.Recovery });
|
||||
} catch (error) {
|
||||
trackError(error, Submit.EmailChangeLocale);
|
||||
addNotification({
|
||||
|
||||
+11
-4
@@ -7,14 +7,18 @@
|
||||
import { Id } from '$lib/components';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Layout, Card } from '@appwrite.io/pink-svelte';
|
||||
import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ProjectEmailTemplateLocale,
|
||||
ProjectEmailTemplateId,
|
||||
type Models
|
||||
} from '@appwrite.io/console';
|
||||
|
||||
export let loading = false;
|
||||
export let project: Models.Project;
|
||||
export let localeCodes: Models.LocaleCode[];
|
||||
|
||||
let isUpdating = false;
|
||||
let locale = EmailTemplateLocale.En;
|
||||
let locale = ProjectEmailTemplateLocale.En;
|
||||
|
||||
async function onLocaleChange() {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -24,12 +28,15 @@
|
||||
const template = await loadEmailTemplate(
|
||||
project.region,
|
||||
project.$id,
|
||||
EmailTemplateType.SessionAlert,
|
||||
ProjectEmailTemplateId.SessionAlert,
|
||||
locale
|
||||
);
|
||||
emailTemplate.set(template);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.SessionAlert });
|
||||
trackEvent(Submit.EmailChangeLocale, {
|
||||
locale,
|
||||
type: ProjectEmailTemplateId.SessionAlert
|
||||
});
|
||||
} catch (error) {
|
||||
trackError(error, Submit.EmailChangeLocale);
|
||||
addNotification({
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
import { baseEmailTemplate, emailTemplate } from './store';
|
||||
import deepEqual from 'deep-equal';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { EmailTemplateLocale, type EmailTemplateType, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ProjectEmailTemplateLocale,
|
||||
type ProjectEmailTemplateId,
|
||||
type Models
|
||||
} from '@appwrite.io/console';
|
||||
import { Icon, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import TemplateSkeleton from './templateSkeleton.svelte';
|
||||
@@ -29,7 +33,8 @@
|
||||
const isButtonDisabled = $derived(deepEqual($emailTemplate, $baseEmailTemplate));
|
||||
|
||||
async function saveEmailTemplate() {
|
||||
const locale = ($emailTemplate.locale || EmailTemplateLocale.En) as EmailTemplateLocale;
|
||||
const locale = ($emailTemplate.locale ||
|
||||
ProjectEmailTemplateLocale.En) as ProjectEmailTemplateLocale;
|
||||
|
||||
// TODO: uncomment after SDK is updated
|
||||
// if (!isValueOfStringEnum(TemplateType, $emailTemplate.type)) {
|
||||
@@ -54,7 +59,7 @@
|
||||
break;
|
||||
}
|
||||
await sdk.forProject(project.region, project.$id).project.updateEmailTemplate({
|
||||
templateId: $emailTemplate.type as EmailTemplateType,
|
||||
templateId: $emailTemplate.type as ProjectEmailTemplateId,
|
||||
locale,
|
||||
subject: $emailTemplate.subject || undefined,
|
||||
message: $emailTemplate.message || undefined,
|
||||
|
||||
+11
-4
@@ -7,14 +7,18 @@
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Layout, Card } from '@appwrite.io/pink-svelte';
|
||||
import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ProjectEmailTemplateLocale,
|
||||
ProjectEmailTemplateId,
|
||||
type Models
|
||||
} from '@appwrite.io/console';
|
||||
|
||||
export let loading = false;
|
||||
export let project: Models.Project;
|
||||
export let localeCodes: Models.LocaleCode[];
|
||||
|
||||
let isUpdating = false;
|
||||
let locale = EmailTemplateLocale.En;
|
||||
let locale = ProjectEmailTemplateLocale.En;
|
||||
|
||||
async function onLocaleChange() {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -24,12 +28,15 @@
|
||||
const template = await loadEmailTemplate(
|
||||
project.region,
|
||||
project.$id,
|
||||
EmailTemplateType.Verification,
|
||||
ProjectEmailTemplateId.Verification,
|
||||
locale
|
||||
);
|
||||
emailTemplate.set(template);
|
||||
$baseEmailTemplate = { ...$emailTemplate };
|
||||
trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Verification });
|
||||
trackEvent(Submit.EmailChangeLocale, {
|
||||
locale,
|
||||
type: ProjectEmailTemplateId.Verification
|
||||
});
|
||||
} catch (error) {
|
||||
trackError(error, Submit.EmailChangeLocale);
|
||||
addNotification({
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { EmailTemplateType, type EmailTemplateLocale, type Models } from '@appwrite.io/console';
|
||||
import {
|
||||
ProjectEmailTemplateId,
|
||||
type ProjectEmailTemplateLocale,
|
||||
type Models
|
||||
} from '@appwrite.io/console';
|
||||
|
||||
// component imports
|
||||
import Email2FaTemplate from './email2FATemplate.svelte';
|
||||
@@ -11,9 +15,9 @@ import EmailVerificationTemplate from './emailVerificationTemplate.svelte';
|
||||
import EmailSessionAlertTemplate from './emailSessionAlertTemplate.svelte';
|
||||
|
||||
export type EmailTemplateForm = Models.EmailTemplate & {
|
||||
type: EmailTemplateType | null;
|
||||
templateId: EmailTemplateType | string | null;
|
||||
locale: EmailTemplateLocale | string | null;
|
||||
type: ProjectEmailTemplateId | null;
|
||||
templateId: ProjectEmailTemplateId | string | null;
|
||||
locale: ProjectEmailTemplateLocale | string | null;
|
||||
};
|
||||
|
||||
export const emailTemplate = writable<EmailTemplateForm>({
|
||||
@@ -42,44 +46,44 @@ export const baseEmailTemplate = writable<EmailTemplateForm>({
|
||||
|
||||
export const templates = [
|
||||
{
|
||||
key: EmailTemplateType.Verification,
|
||||
key: ProjectEmailTemplateId.Verification,
|
||||
title: 'Verification',
|
||||
description:
|
||||
'Send a verification email to users that sign in with their email and password.',
|
||||
component: EmailVerificationTemplate
|
||||
},
|
||||
{
|
||||
key: EmailTemplateType.MagicSession,
|
||||
key: ProjectEmailTemplateId.MagicSession,
|
||||
title: 'Magic URL',
|
||||
description: 'Send an email to users that sign in with a magic URL.',
|
||||
component: EmailMagicUrlTemplate
|
||||
},
|
||||
{
|
||||
key: EmailTemplateType.OtpSession,
|
||||
key: ProjectEmailTemplateId.OtpSession,
|
||||
title: 'OTP session',
|
||||
description: 'Send an email to users that sign in with a email OTP.',
|
||||
component: EmailOtpSessionTemplate
|
||||
},
|
||||
{
|
||||
key: EmailTemplateType.Recovery,
|
||||
key: ProjectEmailTemplateId.Recovery,
|
||||
title: 'Reset password',
|
||||
description: 'Send a recovery email to users that forget their password.',
|
||||
component: EmailRecoveryTemplate
|
||||
},
|
||||
{
|
||||
key: EmailTemplateType.Invitation,
|
||||
key: ProjectEmailTemplateId.Invitation,
|
||||
title: 'Invite user',
|
||||
description: 'Send an invitation email to become a member of your project.',
|
||||
component: EmailInviteTemplate
|
||||
},
|
||||
{
|
||||
key: EmailTemplateType.MfaChallenge,
|
||||
key: ProjectEmailTemplateId.MfaChallenge,
|
||||
title: '2FA verification',
|
||||
description: 'Send a two-factor authentication email to a user.',
|
||||
component: Email2FaTemplate
|
||||
},
|
||||
{
|
||||
key: EmailTemplateType.SessionAlert,
|
||||
key: ProjectEmailTemplateId.SessionAlert,
|
||||
title: 'Session alert',
|
||||
description: 'Send an email to users when a new session is created.',
|
||||
component: EmailSessionAlertTemplate,
|
||||
|
||||
@@ -4,14 +4,21 @@ import { Dependencies } from '$lib/constants';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { OAuthProvider, type Models } from '@appwrite.io/console';
|
||||
import { OAuthProvider, type Models as ConsoleModels } from '@appwrite.io/console';
|
||||
|
||||
type ProjectOAuthProvider = ConsoleModels.OAuth2ProviderList['providers'][number];
|
||||
|
||||
export type AuthProvider = ProjectOAuthProvider & {
|
||||
key: ProjectOAuthProvider['$id'];
|
||||
name: string;
|
||||
};
|
||||
|
||||
type Args = {
|
||||
region: string;
|
||||
projectId: string;
|
||||
provider: Models.AuthProvider;
|
||||
provider: AuthProvider;
|
||||
appId: string | null;
|
||||
secret: string;
|
||||
secret: string | null;
|
||||
details: Record<string, string>;
|
||||
enabled: boolean;
|
||||
};
|
||||
@@ -21,7 +28,7 @@ type Return = {
|
||||
message?: string;
|
||||
};
|
||||
|
||||
function parseSecret(secret: string) {
|
||||
function parseSecret(secret: string | null) {
|
||||
if (!secret) return {};
|
||||
|
||||
try {
|
||||
|
||||
+8
-4
@@ -10,16 +10,20 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { func } from '../store';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import { Runtime, type Scopes as ScopesType } from '@appwrite.io/console';
|
||||
import {
|
||||
Runtime,
|
||||
type ProjectKeyScopes,
|
||||
type Scopes as FunctionScopes
|
||||
} from '@appwrite.io/console';
|
||||
import Scopes from '$routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { Link } from '$lib/elements';
|
||||
|
||||
const functionId = page.params.function;
|
||||
let functionScopes: ScopesType[] = null;
|
||||
let functionScopes: ProjectKeyScopes[] = null;
|
||||
|
||||
onMount(async () => {
|
||||
functionScopes ??= $func.scopes as ScopesType[];
|
||||
functionScopes ??= $func.scopes as ProjectKeyScopes[];
|
||||
});
|
||||
|
||||
async function updateScopes() {
|
||||
@@ -39,7 +43,7 @@
|
||||
logging: $func.logging || undefined,
|
||||
entrypoint: $func.entrypoint || undefined,
|
||||
commands: $func.commands || undefined,
|
||||
scopes: functionScopes || undefined,
|
||||
scopes: (functionScopes as unknown as FunctionScopes[]) || undefined,
|
||||
installationId: $func.installationId || undefined,
|
||||
providerRepositoryId: $func.providerRepositoryId || undefined,
|
||||
providerBranch: $func.providerBranch || undefined,
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
<title>Console - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
<Onboard platforms={page.data.project.platforms} pingCount={page.data.project.pingCount} />
|
||||
<Onboard platforms={page.data.platforms.platforms} pingCount={page.data.project.pingCount} />
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { writable } from 'svelte/store';
|
||||
import Scopes from '../api-keys/scopes.svelte';
|
||||
import { ID, type Scopes as ScopesType } from '@appwrite.io/console';
|
||||
import ScopesSelector from '../api-keys/scopes.svelte';
|
||||
import { ID, type ProjectKeyScopes } from '@appwrite.io/console';
|
||||
import { page } from '$app/state';
|
||||
import { copy } from '$lib/helpers/copy';
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
let formComponent: Form;
|
||||
let isSubmitting = writable(false);
|
||||
|
||||
let scopes: ScopesType[] = [];
|
||||
let scopes: ProjectKeyScopes[] = [];
|
||||
let name = '';
|
||||
let expire: string | null = null;
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
Choose which permission scopes to grant your application. It is best
|
||||
practice to allow only the permissions you need to meet your project goals.
|
||||
</Typography.Text>
|
||||
<Scopes bind:scopes />
|
||||
<ScopesSelector bind:scopes />
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
</Layout.Stack>
|
||||
|
||||
+5
-5
@@ -12,16 +12,16 @@
|
||||
import { project } from '../../store';
|
||||
import Delete from './delete.svelte';
|
||||
import UpdateExpirationDate from './updateExpirationDate.svelte';
|
||||
import type { Models, Scopes as ScopesType } from '@appwrite.io/console';
|
||||
import type { Models, ProjectKeyScopes } from '@appwrite.io/console';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import Scopes from '../api-keys/scopes.svelte';
|
||||
import ScopesSelector from '../api-keys/scopes.svelte';
|
||||
import { InteractiveText, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let key: Models.DevKey | Models.Key;
|
||||
export let keyType: 'api' | 'dev' = 'api';
|
||||
|
||||
let name: string = null;
|
||||
let scopes: ScopesType[] = null;
|
||||
let scopes: ProjectKeyScopes[] = null;
|
||||
|
||||
let showDelete = false;
|
||||
const isApiKey = keyType === 'api';
|
||||
@@ -32,7 +32,7 @@
|
||||
onMount(() => {
|
||||
name ??= key.name;
|
||||
if (isApiKey) {
|
||||
scopes ??= (key as Models.Key).scopes as ScopesType[];
|
||||
scopes ??= (key as Models.Key).scopes as ProjectKeyScopes[];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
to allow only the permissions you need to meet your project goals.
|
||||
<svelte:fragment slot="aside">
|
||||
{#if scopes !== null}
|
||||
<Scopes bind:scopes />
|
||||
<ScopesSelector bind:scopes />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Empty, MultiSelectionTable } from '$lib/components';
|
||||
import { Empty, MultiSelectionTable, PaginationWithLimit } from '$lib/components';
|
||||
import { canWriteKeys } from '$lib/stores/roles';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { diffDays } from '$lib/helpers/date';
|
||||
@@ -14,10 +14,14 @@
|
||||
|
||||
let {
|
||||
keyType = 'api',
|
||||
keys
|
||||
keys,
|
||||
limit,
|
||||
offset
|
||||
}: {
|
||||
keyType?: 'api' | 'dev';
|
||||
keys: Models.KeyList | Models.DevKeyList;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
} = $props();
|
||||
|
||||
let selectedKeys = $state([]);
|
||||
@@ -116,6 +120,13 @@
|
||||
{/each}
|
||||
{/snippet}
|
||||
</MultiSelectionTable>
|
||||
{#if limit !== undefined && offset !== undefined}
|
||||
<PaginationWithLimit
|
||||
name={`${capitalize(label)} keys`}
|
||||
{limit}
|
||||
{offset}
|
||||
total={keys.total} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
import { ExpirationInput } from '$lib/components';
|
||||
import type { Models, Scopes } from '@appwrite.io/console';
|
||||
import type { Models, ProjectKeyScopes } from '@appwrite.io/console';
|
||||
import { page } from '$app/state';
|
||||
|
||||
export let keyType: 'api' | 'dev' = 'api';
|
||||
@@ -31,7 +31,7 @@
|
||||
await sdk.forProject(page.params.region, page.params.project).project.updateKey({
|
||||
keyId: key.$id,
|
||||
name: key.name,
|
||||
scopes: (key as Models.Key).scopes as Scopes[],
|
||||
scopes: (key as Models.Key).scopes as ProjectKeyScopes[],
|
||||
expire: expiration
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -9,4 +9,4 @@
|
||||
setOverviewAction(Action);
|
||||
</script>
|
||||
|
||||
<Table keys={data.keys} />
|
||||
<Table keys={data.keys} limit={data.limit} offset={data.offset} />
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { CARD_LIMIT, Dependencies } from '$lib/constants';
|
||||
import { getLimit, getPage, pageToOffset } from '$lib/helpers/load';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ params, depends }) => {
|
||||
export const load: PageLoad = async ({ params, depends, route, url }) => {
|
||||
depends(Dependencies.KEYS);
|
||||
const projectSdk = sdk.forProject(params.region, params.project).project;
|
||||
const page = getPage(url);
|
||||
const limit = getLimit(url, route, CARD_LIMIT);
|
||||
const offset = pageToOffset(page, limit);
|
||||
|
||||
return {
|
||||
keys: await sdk.forProject(params.region, params.project).project.listKeys()
|
||||
limit,
|
||||
offset,
|
||||
keys: await projectSdk.listKeys({
|
||||
queries: [Query.limit(limit), Query.offset(offset)]
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
@@ -46,9 +46,9 @@
|
||||
Selector,
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import type { Scopes } from '@appwrite.io/console';
|
||||
import type { ProjectKeyScopes } from '@appwrite.io/console';
|
||||
|
||||
let { scopes = $bindable([]) }: { scopes: Scopes[] } = $props();
|
||||
let { scopes = $bindable([]) }: { scopes: ProjectKeyScopes[] } = $props();
|
||||
|
||||
let allScopesList: ScopeDefinition[] = $state([]);
|
||||
let mounted = $state(false);
|
||||
@@ -192,10 +192,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
function generateSyncedScopes(activeScopesObj: Record<string, boolean>): Scopes[] {
|
||||
function generateSyncedScopes(activeScopesObj: Record<string, boolean>): ProjectKeyScopes[] {
|
||||
return Object.entries(activeScopesObj)
|
||||
.filter(([scope, isActive]) => isActive && scopeCatalog.has(scope))
|
||||
.map(([scope]) => scope as Scopes);
|
||||
.map(([scope]) => scope as ProjectKeyScopes);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -93,7 +93,8 @@
|
||||
import {
|
||||
type DeleteOperationState,
|
||||
type DeleteOperation,
|
||||
MultiSelectionTable
|
||||
MultiSelectionTable,
|
||||
PaginationWithLimit
|
||||
} from '$lib/components';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Submit, trackError } from '$lib/actions/analytics';
|
||||
@@ -200,6 +201,11 @@
|
||||
{/each}
|
||||
{/snippet}
|
||||
</MultiSelectionTable>
|
||||
<PaginationWithLimit
|
||||
name="Platforms"
|
||||
limit={data.limit}
|
||||
offset={data.offset}
|
||||
total={data.platforms.total} />
|
||||
{:else}
|
||||
<Card.Base padding="none">
|
||||
<Empty
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import { CARD_LIMIT, Dependencies } from '$lib/constants';
|
||||
import { getLimit, getPage, pageToOffset } from '$lib/helpers/load';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const { project } = await parent();
|
||||
export const load: PageLoad = async ({ depends, params, route, url }) => {
|
||||
depends(Dependencies.PROJECT);
|
||||
const page = getPage(url);
|
||||
const limit = getLimit(url, route, CARD_LIMIT);
|
||||
const offset = pageToOffset(page, limit);
|
||||
|
||||
return {
|
||||
platforms: {
|
||||
platforms: project.platforms,
|
||||
total: project.platforms.length
|
||||
}
|
||||
limit,
|
||||
offset,
|
||||
platforms: await sdk.forProject(params.region, params.project).project.listPlatforms({
|
||||
queries: [Query.limit(limit), Query.offset(offset)]
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import Details from './details.svelte';
|
||||
import ExportModal from './exportModal.svelte';
|
||||
import { readOnly } from '$lib/stores/billing';
|
||||
import { ID, Scopes, type Models } from '@appwrite.io/console';
|
||||
import { ID, ProjectKeyScopes, type Models } from '@appwrite.io/console';
|
||||
import { canWriteProjects } from '$lib/stores/roles';
|
||||
import {
|
||||
IconCloud,
|
||||
@@ -92,23 +92,23 @@
|
||||
keyId: ID.unique(),
|
||||
name: `[AUTO-GENERATED] Migration ${new Date().toISOString()}`,
|
||||
scopes: [
|
||||
Scopes.UsersRead,
|
||||
Scopes.TeamsRead,
|
||||
Scopes.DatabasesRead,
|
||||
Scopes.CollectionsRead /* legacy */,
|
||||
Scopes.AttributesRead /* legacy */,
|
||||
Scopes.IndexesRead,
|
||||
Scopes.DocumentsRead /* legacy */,
|
||||
Scopes.TablesRead,
|
||||
Scopes.ColumnsRead,
|
||||
Scopes.RowsRead,
|
||||
Scopes.FilesRead,
|
||||
Scopes.BucketsRead,
|
||||
Scopes.FunctionsRead,
|
||||
Scopes.ExecutionsRead,
|
||||
Scopes.LocaleRead,
|
||||
Scopes.AvatarsRead,
|
||||
Scopes.HealthRead
|
||||
ProjectKeyScopes.UsersRead,
|
||||
ProjectKeyScopes.TeamsRead,
|
||||
ProjectKeyScopes.DatabasesRead,
|
||||
ProjectKeyScopes.CollectionsRead /* legacy */,
|
||||
ProjectKeyScopes.AttributesRead /* legacy */,
|
||||
ProjectKeyScopes.IndexesRead,
|
||||
ProjectKeyScopes.DocumentsRead /* legacy */,
|
||||
ProjectKeyScopes.TablesRead,
|
||||
ProjectKeyScopes.ColumnsRead,
|
||||
ProjectKeyScopes.RowsRead,
|
||||
ProjectKeyScopes.FilesRead,
|
||||
ProjectKeyScopes.BucketsRead,
|
||||
ProjectKeyScopes.FunctionsRead,
|
||||
ProjectKeyScopes.ExecutionsRead,
|
||||
ProjectKeyScopes.LocaleRead,
|
||||
ProjectKeyScopes.AvatarsRead,
|
||||
ProjectKeyScopes.HealthRead
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
+23
-23
@@ -10,7 +10,7 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { ID, Scopes } from '@appwrite.io/console';
|
||||
import { ID, ProjectKeyScopes } from '@appwrite.io/console';
|
||||
|
||||
export let show = false;
|
||||
|
||||
@@ -94,28 +94,28 @@
|
||||
keyId: ID.unique(),
|
||||
name: `[AUTO-GENERATED] Migration ${new Date().toISOString()}`,
|
||||
scopes: [
|
||||
Scopes.UsersRead,
|
||||
Scopes.TeamsRead,
|
||||
Scopes.DatabasesRead,
|
||||
Scopes.CollectionsRead /* legacy */,
|
||||
Scopes.AttributesRead /* legacy */,
|
||||
Scopes.IndexesRead,
|
||||
Scopes.DocumentsRead /* legacy */,
|
||||
Scopes.TablesRead,
|
||||
Scopes.ColumnsRead,
|
||||
Scopes.RowsRead,
|
||||
Scopes.FilesRead,
|
||||
Scopes.BucketsRead,
|
||||
Scopes.FunctionsRead,
|
||||
Scopes.ExecutionsRead,
|
||||
Scopes.SitesRead,
|
||||
Scopes.ProvidersRead,
|
||||
Scopes.TopicsRead,
|
||||
Scopes.SubscribersRead,
|
||||
Scopes.MessagesRead,
|
||||
Scopes.LocaleRead,
|
||||
Scopes.AvatarsRead,
|
||||
Scopes.HealthRead
|
||||
ProjectKeyScopes.UsersRead,
|
||||
ProjectKeyScopes.TeamsRead,
|
||||
ProjectKeyScopes.DatabasesRead,
|
||||
ProjectKeyScopes.CollectionsRead /* legacy */,
|
||||
ProjectKeyScopes.AttributesRead /* legacy */,
|
||||
ProjectKeyScopes.IndexesRead,
|
||||
ProjectKeyScopes.DocumentsRead /* legacy */,
|
||||
ProjectKeyScopes.TablesRead,
|
||||
ProjectKeyScopes.ColumnsRead,
|
||||
ProjectKeyScopes.RowsRead,
|
||||
ProjectKeyScopes.FilesRead,
|
||||
ProjectKeyScopes.BucketsRead,
|
||||
ProjectKeyScopes.FunctionsRead,
|
||||
ProjectKeyScopes.ExecutionsRead,
|
||||
ProjectKeyScopes.SitesRead,
|
||||
ProjectKeyScopes.ProvidersRead,
|
||||
ProjectKeyScopes.TopicsRead,
|
||||
ProjectKeyScopes.SubscribersRead,
|
||||
ProjectKeyScopes.MessagesRead,
|
||||
ProjectKeyScopes.LocaleRead,
|
||||
ProjectKeyScopes.AvatarsRead,
|
||||
ProjectKeyScopes.HealthRead
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import deepEqual from 'deep-equal';
|
||||
import { currentPlan } from '$lib/stores/organization';
|
||||
import { type Secure } from '@appwrite.io/console';
|
||||
import { type ProjectSMTPSecure } from '@appwrite.io/console';
|
||||
import InputSelect from '$lib/elements/forms/inputSelect.svelte';
|
||||
import { getChangePlanUrl } from '$lib/stores/billing';
|
||||
import { Link, Selector, Alert } from '@appwrite.io/pink-svelte';
|
||||
@@ -85,7 +85,7 @@
|
||||
port: port || undefined,
|
||||
username: username || undefined,
|
||||
password: password || undefined,
|
||||
secure: secure ? (secure as Secure) : undefined
|
||||
secure: secure ? (secure as ProjectSMTPSecure) : undefined
|
||||
});
|
||||
|
||||
invalidate(Dependencies.PROJECT);
|
||||
@@ -212,7 +212,7 @@
|
||||
placeholder="Enter password" />
|
||||
<InputSelect
|
||||
id="tls"
|
||||
label="Secure protocol"
|
||||
label="ProjectSMTPSecure protocol"
|
||||
placeholder="Select protocol"
|
||||
bind:value={secure}
|
||||
{options} />
|
||||
|
||||
@@ -11,18 +11,18 @@
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import { Dialog, Divider, Layout, Spinner } from '@appwrite.io/pink-svelte';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import { ProtocolId } from '@appwrite.io/console';
|
||||
import { ProjectProtocolId } from '@appwrite.io/console';
|
||||
import { get } from 'svelte/store';
|
||||
import { canWriteProjects } from '$lib/stores/roles';
|
||||
|
||||
let isUpdatingAllProtocols = $state(false);
|
||||
let showUpdateProtocolDialog = $state(false);
|
||||
let updateProtocolsEnabledMode = $state<boolean | null>(null);
|
||||
let apiProtocolUpdates = new SvelteSet<ProtocolId>();
|
||||
const protocolDescriptions: Record<ProtocolId, string> = {
|
||||
[ProtocolId.Rest]: 'Standard HTTP API requests from client SDKs.',
|
||||
[ProtocolId.Graphql]: 'GraphQL API access for queries and mutations.',
|
||||
[ProtocolId.Websocket]: 'Realtime subscriptions over WebSocket connections.'
|
||||
let apiProtocolUpdates = new SvelteSet<ProjectProtocolId>();
|
||||
const protocolDescriptions: Record<ProjectProtocolId, string> = {
|
||||
[ProjectProtocolId.Rest]: 'Standard HTTP API requests from client SDKs.',
|
||||
[ProjectProtocolId.Graphql]: 'GraphQL API access for queries and mutations.',
|
||||
[ProjectProtocolId.Websocket]: 'Realtime subscriptions over WebSocket connections.'
|
||||
};
|
||||
const isAnyProtocolUpdating = $derived(apiProtocolUpdates.size > 0);
|
||||
const isAnyUpdateInProgress = $derived(isUpdatingAllProtocols || isAnyProtocolUpdating);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { project } from '../store';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import { Dialog, Divider, Layout, Spinner } from '@appwrite.io/pink-svelte';
|
||||
import type { ServiceId } from '@appwrite.io/console';
|
||||
import type { ProjectServiceId } from '@appwrite.io/console';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
@@ -20,7 +20,7 @@
|
||||
let showUpdateServiceDialog = $state(false);
|
||||
let updateServicesEnabledMode = $state<boolean | null>(null);
|
||||
|
||||
let apiServiceUpdates = new SvelteSet<ServiceId>();
|
||||
let apiServiceUpdates = new SvelteSet<ProjectServiceId>();
|
||||
|
||||
const isAnyServiceUpdating = $derived(apiServiceUpdates.size > 0);
|
||||
const isAnyUpdateInProgress = $derived(isUpdatingAllServices || isAnyServiceUpdating);
|
||||
|
||||
@@ -22,8 +22,8 @@ export const projectRegion = derived(project, ($project) => {
|
||||
});
|
||||
|
||||
export const onboarding = derived(
|
||||
project,
|
||||
($project) => $project?.platforms?.length === 0 && $project?.keys?.length === 0
|
||||
page,
|
||||
($page) => $page.data.platforms?.total === 0 && $page.data.keys?.total === 0
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,21 +2,39 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import { Layout, Spinner, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(async () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
const projectId = params.get('projectId');
|
||||
const origin = params.get('origin');
|
||||
const path = params.get('path');
|
||||
try {
|
||||
await sdk.forConsole.projects.get({ projectId });
|
||||
const { projects } = await sdk.forConsole.projects.list({
|
||||
queries: [
|
||||
Query.equal('$id', projectId),
|
||||
Query.limit(1),
|
||||
Query.select(['$id', 'region'])
|
||||
],
|
||||
total: false
|
||||
});
|
||||
|
||||
const project = projects[0];
|
||||
|
||||
if (!project) {
|
||||
await goto(
|
||||
`${base}/auth/preview/access?origin=${origin}&path=${path}&projectId=${projectId}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await sdk.forProject(project.region ?? 'default', projectId).project.get();
|
||||
const jwt = await sdk.forConsole.account.createJWT();
|
||||
window.location.href = `${origin}/_appwrite/authorize?jwt=${jwt.jwt}&path=${path}`;
|
||||
} catch {
|
||||
goto(
|
||||
await goto(
|
||||
`${base}/auth/preview/access?origin=${origin}&path=${path}&projectId=${projectId}`
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user