Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/frontend/src/lib/components/auth/AuthConfig.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
let derivationOrigin = $derived(
fromNullishNullable(fromNullishNullable(config?.internet_identity)?.derivation_origin)
);
let externalAlternativeOrigins = $derived(
fromNullishNullable(
fromNullishNullable(config?.internet_identity)?.external_alternative_origins
)
);

const loadConfig = async () => {
const result = await getAuthConfig({
Expand Down Expand Up @@ -91,7 +96,7 @@
<div class="card-container with-title">
<span class="title">{$i18n.core.config}</span>

<div class="columns-3 fit-column-1">
<div class="content">
<div>
{#if supportConfig}
<div in:fade>
Expand All @@ -107,6 +112,18 @@
{/if}
</Value>
</div>

{#if nonNullish(externalAlternativeOrigins)}
<div in:fade>
<Value>
{#snippet label()}
{$i18n.authentication.external_alternative_origins}
{/snippet}

<p>{externalAlternativeOrigins.join(',')}</p>
</Value>
</div>
{/if}
{/if}

{#if supportSettings}
Expand Down Expand Up @@ -135,7 +152,13 @@
{/if}

<style lang="scss">
@use '../../styles/mixins/text';

.card-container {
min-height: 166px;
}

p {
@include text.truncate;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script lang="ts">
import { fromNullable, fromNullishNullable, isEmptyString } from '@dfinity/utils';
import { onMount, type SvelteComponent } from 'svelte';
import type { AuthenticationConfig } from '$declarations/satellite/satellite.did';
import Collapsible from '$lib/components/ui/Collapsible.svelte';
import Input from '$lib/components/ui/Input.svelte';
import Value from '$lib/components/ui/Value.svelte';
import { i18n } from '$lib/stores/i18n.store';

interface Props {
config: AuthenticationConfig | undefined;
externalAlternativeOrigins: string;
}

let { config, externalAlternativeOrigins = $bindable('') }: Props = $props();

let externalAlternativeOriginsInput = $state(
(
fromNullable(
fromNullishNullable(config?.internet_identity)?.external_alternative_origins ?? []
) ?? []
).join(',')
);

$effect(() => {
externalAlternativeOrigins = externalAlternativeOriginsInput;
});

let collapsibleRef: (SvelteComponent & { open: () => void; close: () => void }) | undefined =
$state(undefined);

onMount(() => {
if (isEmptyString(externalAlternativeOriginsInput)) {
return;
}

collapsibleRef?.open();
});
</script>

<Collapsible bind:this={collapsibleRef}>
<svelte:fragment slot="header">{$i18n.core.advanced_options}</svelte:fragment>

<div>
<Value>
{#snippet label()}
{$i18n.authentication.external_alternative_origins}
{/snippet}

<Input
inputType="text"
name="destination"
required={false}
placeholder={$i18n.authentication.external_alternative_origins_placeholder}
bind:value={externalAlternativeOriginsInput}
/>
</Value>
</div>
</Collapsible>
7 changes: 6 additions & 1 deletion src/frontend/src/lib/components/auth/AuthConfigForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { fade } from 'svelte/transition';
import type { Satellite } from '$declarations/mission_control/mission_control.did';
import type { AuthenticationConfig, Rule } from '$declarations/satellite/satellite.did';
import AuthConfigAdvancedOptions from '$lib/components/auth/AuthConfigAdvancedOptions.svelte';
import Input from '$lib/components/ui/Input.svelte';
import Value from '$lib/components/ui/Value.svelte';
import Warning from '$lib/components/ui/Warning.svelte';
Expand All @@ -17,6 +18,7 @@
satellite: Satellite;
rule: Rule | undefined;
maxTokens: number | undefined;
externalAlternativeOrigins: string;
onsubmit: ($event: SubmitEvent) => Promise<void>;
}

Expand All @@ -26,7 +28,8 @@
config,
satellite,
rule,
maxTokens = $bindable(undefined)
maxTokens = $bindable(undefined),
externalAlternativeOrigins = $bindable('')
}: Props = $props();

let satelliteUrl: URL | null = $derived(
Expand Down Expand Up @@ -121,6 +124,8 @@
</div>
{/if}

<AuthConfigAdvancedOptions {config} bind:externalAlternativeOrigins />

{#if warnDerivationOrigin}
<div class="warn" in:fade>
<Warning>{$i18n.authentication.main_domain_warn}</Warning>
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/src/lib/components/modals/AuthConfigModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@

let selectedDerivationOrigin = $state<URL | undefined>(undefined);

let externalAlternativeOrigins = $state('');

let step: 'init' | 'in_progress' | 'ready' | 'error' = $state('init');

const handleSubmit = async ($event: SubmitEvent) => {
Expand All @@ -44,6 +46,7 @@
maxTokens,
rule,
derivationOrigin: selectedDerivationOrigin,
externalAlternativeOrigins,
config
});

Expand Down Expand Up @@ -78,6 +81,7 @@
onsubmit={handleSubmit}
bind:maxTokens
bind:selectedDerivationOrigin
bind:externalAlternativeOrigins
/>
{/if}
</Modal>
Expand Down
7 changes: 5 additions & 2 deletions src/frontend/src/lib/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,9 @@
"main_domain": "Main domain (\"derivation origin\")",
"not_configured": "Not configured",
"edit_configuration": "Update the configuration of your authentication using the form below.",
"main_domain_warn": "You are about to change your main domain. Please ensure this is the desired action, as all existing users will no longer be recognized by your app."
"main_domain_warn": "You are about to change your main domain. Please ensure this is the desired action, as all existing users will no longer be recognized by your app.",
"external_alternative_origins": "External alternative origins",
"external_alternative_origins_placeholder": "A comma-separated list of external domains to allow for derivation"
},
"datastore": {
"title": "Datastore",
Expand Down Expand Up @@ -544,7 +546,8 @@
"auth_settings_not_loaded": "The authentication's settings are not yet loaded.",
"auth_rate_config_max_tokens": "A maximal number of updates per minute is required.",
"auth_rate_config_update": "Unexpected error(s) while trying to update your authentication settings.",
"auth_domain_config": "Unexpected error(s) while trying to update your authentication configuration",
"auth_domain_config": "Unexpected error(s) while trying to update your authentication configuration.",
"auth_external_alternative_origins": "External alternative origins are malformed. Please provide a valid domain without a protocol.",
"mission_control_not_loaded": "Your mission control is not yet loaded.",
"mission_control_settings_not_loaded": "The settings for the mission control are not yet loaded.",
"mission_control_user_data_not_loaded": "Your data (metadata, config, etc) for the mission control are not yet loaded.",
Expand Down
7 changes: 5 additions & 2 deletions src/frontend/src/lib/i18n/zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,9 @@
"main_domain": "主域名(\"derivation origin\")",
"not_configured": "未配置",
"edit_configuration": "用如下表格更新你的认证配置.",
"main_domain_warn": "你将要修改主域名. 请再次确认, 现有所有用户将无法识别由于修改."
"main_domain_warn": "你将要修改主域名. 请再次确认, 现有所有用户将无法识别由于修改.",
"external_alternative_origins": "External alternative origins",
"external_alternative_origins_placeholder": "A comma-separated list of external domains to allow for derivation"
},
"datastore": {
"title": "数据池",
Expand Down Expand Up @@ -544,7 +546,8 @@
"auth_settings_not_loaded": "认证设置未被加载.",
"auth_rate_config_max_tokens": "每分钟最多更新操作数量是必须的.",
"auth_rate_config_update": "更改认证配置出现异常错误.",
"auth_domain_config": "更新认证配置是出现异常错误",
"auth_domain_config": "更新认证配置是出现异常错误.",
"auth_external_alternative_origins": "External alternative origins are malformed. Please provide a valid domain without a protocol.",
"mission_control_not_loaded": "Your mission control is not yet loaded.",
"mission_control_settings_not_loaded": "The settings for the mission control are not yet loaded.",
"mission_control_user_data_not_loaded": "Your data (metadata, config, etc) for the mission control are not yet loaded.",
Expand Down
20 changes: 18 additions & 2 deletions src/frontend/src/lib/services/auth.config.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { toasts } from '$lib/stores/toasts.store';
import type { OptionIdentity } from '$lib/types/itentity';
import type { Option } from '$lib/types/utils';
import {
assertExternalAlternativeOrigins,
buildDeleteAuthenticationConfig,
buildSetAuthenticationConfig
} from '$lib/utils/auth.config.utils';
Expand All @@ -22,6 +23,7 @@ import {
fromNullishNullable,
isNullish,
nonNullish,
notEmptyString,
toNullable
} from '@dfinity/utils';
import { compare } from 'semver';
Expand All @@ -32,6 +34,7 @@ interface UpdateAuthConfigParams {
rule: Rule | undefined;
config: AuthenticationConfig | undefined;
maxTokens: number | undefined;
externalAlternativeOrigins: string;
derivationOrigin: Option<URL>;
identity: OptionIdentity;
}
Expand All @@ -42,6 +45,7 @@ export const updateAuthConfig = async ({
config,
maxTokens,
derivationOrigin,
externalAlternativeOrigins,
identity
}: UpdateAuthConfigParams): Promise<{ success: 'ok' | 'cancelled' | 'error'; err?: unknown }> => {
const labels = get(i18n);
Expand All @@ -51,6 +55,16 @@ export const updateAuthConfig = async ({
return { success: 'error' };
}

const externalOrigins = notEmptyString(externalAlternativeOrigins)
? externalAlternativeOrigins.split(',').map((origin) => origin.trim())
: [];
const { valid } = assertExternalAlternativeOrigins(externalOrigins);

if (!valid) {
toasts.error({ text: labels.errors.auth_external_alternative_origins });
return { success: 'error' };
}

const { result: resultRule } = await updateRule({
satellite,
maxTokens,
Expand All @@ -65,6 +79,7 @@ export const updateAuthConfig = async ({
const { result: resultConfig } = await updateConfig({
satellite,
derivationOrigin,
externalOrigins,
config,
identity
});
Expand All @@ -80,9 +95,10 @@ const updateConfig = async ({
satellite: { satellite_id: satelliteId },
config,
derivationOrigin,
externalOrigins,
identity
}: Pick<UpdateAuthConfigParams, 'config' | 'derivationOrigin' | 'satellite'> &
Required<Pick<UpdateAuthConfigParams, 'identity'>>): Promise<{
Required<Pick<UpdateAuthConfigParams, 'identity'>> & { externalOrigins: string[] }): Promise<{
result: 'skip' | 'success' | 'error';
err?: unknown;
}> => {
Expand All @@ -99,7 +115,7 @@ const updateConfig = async ({

const editConfig = nonNullish(derivationOrigin)
? // We use the host in the backend satellite which parse the url with https to generate the /.well-known/ii-alternative-origins
buildSetAuthenticationConfig({ config, domainName: derivationOrigin.host })
buildSetAuthenticationConfig({ config, domainName: derivationOrigin.host, externalOrigins })
: nonNullish(config)
? buildDeleteAuthenticationConfig(config)
: undefined;
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/lib/types/i18n.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ interface I18nAuthentication {
not_configured: string;
edit_configuration: string;
main_domain_warn: string;
external_alternative_origins: string;
external_alternative_origins_placeholder: string;
}

interface I18nDatastore {
Expand Down Expand Up @@ -561,6 +563,7 @@ interface I18nErrors {
auth_rate_config_max_tokens: string;
auth_rate_config_update: string;
auth_domain_config: string;
auth_external_alternative_origins: string;
mission_control_not_loaded: string;
mission_control_settings_not_loaded: string;
mission_control_user_data_not_loaded: string;
Expand Down
29 changes: 24 additions & 5 deletions src/frontend/src/lib/utils/auth.config.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@ import { fromNullable, isNullish, nonNullish, toNullable } from '@dfinity/utils'

export const buildSetAuthenticationConfig = ({
config,
domainName
domainName,
externalOrigins
}: {
config: AuthenticationConfig | undefined;
domainName: string;
}): AuthenticationConfig =>
isNullish(config)
externalOrigins?: string[];
}): AuthenticationConfig => {
const external_alternative_origins: [] | [string[]] =
isNullish(externalOrigins) || externalOrigins.length === 0
? toNullable()
: toNullable(externalOrigins);

return isNullish(config)
? {
internet_identity: [
{
derivation_origin: [domainName],
external_alternative_origins: toNullable()
external_alternative_origins
}
]
}
Expand All @@ -24,11 +31,12 @@ export const buildSetAuthenticationConfig = ({
{
...fromNullable(config.internet_identity),
derivation_origin: [domainName],
external_alternative_origins: toNullable()
external_alternative_origins
}
]
})
};
};

export const buildDeleteAuthenticationConfig = (
config: AuthenticationConfig
Expand All @@ -44,3 +52,14 @@ export const buildDeleteAuthenticationConfig = (
]
})
});

export const assertExternalAlternativeOrigins = (externalOrigins: string[]): { valid: boolean } => {
const invalidUrl = externalOrigins.find((origin) => URL.parse(`https://${origin}`) === null);

const containsProtocol = (origin: string): boolean => /^[a-zA-Z]+:\/\//.test(origin);
const invalidProtocol = externalOrigins.find(containsProtocol);

return {
valid: externalOrigins.length === 0 || (isNullish(invalidUrl) && isNullish(invalidProtocol))
};
};