Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Svelte 5 Migration 9: Dynamic <template> component migrations #703

Merged
merged 9 commits into from
Jan 21, 2025
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions dev_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## CURRENT WORK

### Missing i18n (zh/ko) for Svelte 5 Migration:

- `t.common.close`

## Stage 1 - essentials

[x] finished
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {CSRF_TOKEN} from "../utils/constants";
import {type ErrorResponse} from "$api/response/errorResponse.ts";
import {type ErrorResponse} from "$api/response/error.ts";

export interface IResponse<T> {
body: undefined | T,
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/api/query_params/logout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface LogoutParams {
post_logout_redirect_uri?: string | null,
id_token_hint?: string | null,
state?: string | null,
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface ErrorResponse {
export interface Error {
timestamp: number,
error: string,
message: string,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/api/response/common/language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Language = 'en' | 'de' | 'zhhans' | 'ko';
10 changes: 10 additions & 0 deletions frontend/src/api/response/common/password_policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface PasswordPolicyResponse {
length_min: number,
length_max: number,
include_lower_case?: number,
include_upper_case?: number,
include_digits?: number,
include_special?: number,
valid_days?: number,
not_recently_used?: number,
}
9 changes: 9 additions & 0 deletions frontend/src/api/response/common/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface SessionResponse {
id: string,
user_id?: string,
is_mfa: boolean,
state: 'Open' | 'Init' | 'Auth' | 'LoggedOut' | 'Unknown',
exp: number,
last_seen: number,
remote_ip?: string,
}
41 changes: 41 additions & 0 deletions frontend/src/api/response/common/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type {Language} from "$api/response/common/language.ts";

export type UserAccountTypeResponse =
'New'
| 'Password'
| 'Passkey'
| 'Federated'
| 'FederatedPasskey'
| 'FederatedPassword';

export interface UserResponse {
id: string,
email: string,
given_name: string,
family_name?: string,
language: Language,
roles: string[],
groups?: string[],
enabled: boolean,
email_verified: boolean,
password_expires?: number,
created_at: number,
last_login?: number,
last_failed_login?: number,
failed_login_attempts?: number,
user_expires?: number,
account_type: UserAccountTypeResponse,
webauthn_user_id?: string,
user_values: UserValuesResponse,
auth_provider_id?: string,
federation_uid?: string,
}

export interface UserValuesResponse {
birthdate?: string,
phone?: string,
street?: string,
zip?: number,
city?: string,
country?: string,
}
6 changes: 6 additions & 0 deletions frontend/src/api/templates/AuthProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type AuthProvidersTemplate = AuthProviderTemplate[];

export interface AuthProviderTemplate {
id: string,
name: string,
}
9 changes: 9 additions & 0 deletions frontend/src/api/templates/PasswordReset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type {PasswordPolicyResponse} from "$api/response/common/password_policy.ts";

export interface PasswordResetTemplate {
csrf_token: string,
magic_link_id: string,
needs_mfa: boolean,
password_policy: PasswordPolicyResponse,
user_id: string,
}
3 changes: 0 additions & 3 deletions frontend/src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@
</head>
<body>
<div class="hidden" aria-hidden="true">
<input name="rauthy-csrf-token" id="{{ csrf_token }}" type="hidden"/>
<input name="rauthy-data" id="{{ data }}" type="hidden"/>
<input name="rauthy-action" id="{{ action }}" type="hidden"/>
{%- for tpl in templates -%}
<template id="{{ tpl.id() }}">{{ tpl.inner()|safe }}</template>
{%- endfor %}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/account/AccDevices.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script>
import Devices from "../common/Devices.svelte";

let {sessionInfo = $bindable()} = $props();
let {session = $bindable()} = $props();

</script>

Expand All @@ -10,7 +10,7 @@
This is component is only a wrapper because the same Devices
is reused in the admin ui
-->
<Devices userId={sessionInfo.user_id}/>
<Devices userId={session.user_id}/>
</div>

<style>
Expand Down
30 changes: 5 additions & 25 deletions frontend/src/components/account/AccInfo.svelte
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
<script>
import CheckIcon from "$lib/CheckIcon.svelte";
import {buildWebIdUri, formatDateFromTs, saveProviderToken} from "../../utils/helpers";
import {onMount} from "svelte";
import Button from "$lib/Button.svelte";
import {deleteUserProviderLink, postUserProviderLink} from "../../utils/dataFetching.js";
import Modal from "$lib/Modal.svelte";
import Modal from "$lib5/Modal.svelte";
import getPkce from "oauth-pkce";
import {PKCE_VERIFIER_UPSTREAM} from "../../utils/constants.js";
import {useI18n} from "$state/i18n.svelte";

/**
* @typedef {Object} Props
* @property {any} [user]
* @property {any} authProvider - webIdData will stay undefined if it is not enabled in the backend
* @property {any} webIdData
* @property {boolean} [viewModePhone]
*/

/** @type {Props} */
let {
user = $bindable({}),
user = $bindable(),
providers,
authProvider,
webIdData,
viewModePhone = false
Expand All @@ -29,24 +20,13 @@

let unlinkErr = $state(false);
let showModal = $state(false);
let providersAvailable = $state([]);

let isFederated = $derived(user.account_type?.startsWith('federated'));
let accType = $derived(isFederated ? `${user.account_type}: ${authProvider?.name || ''}` : user.account_type);

let classRow = $derived(viewModePhone ? 'rowPhone' : 'row');
let classLabel = $derived(viewModePhone ? 'labelPhone' : 'label');

onMount(() => {
// value for dev testing only
// let tpl = '[{"id": "7F6N7fb3el3P5XimjJSaeD2o", "name": "Rauthy IAM"}]';
let tpl = document?.getElementsByTagName("template").namedItem("auth_providers")?.innerHTML;
// the additional comparison is just for local dev and no big deal in prod
if (tpl && tpl !== '{{ auth_providers|safe }}') {
providersAvailable = JSON.parse(tpl);
}
})

function linkProvider(id) {
getPkce(64, (error, {challenge, verifier}) => {
if (!error) {
Expand Down Expand Up @@ -130,7 +110,7 @@
</div>
{/if}
</div>
{:else if providersAvailable.length > 0}
{:else if providers.length > 0}
<div
role="button"
tabindex="0"
Expand All @@ -144,7 +124,7 @@
<p>{t.account.providerLinkDesc}</p>

<div class="providers">
{#each providersAvailable as provider (provider.id)}
{#each providers as provider (provider.id)}
<Button on:click={() => linkProvider(provider.id)} level={3}>
<div class="flex-inline">
<img
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/account/AccMFA.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import Tooltip from "$lib/Tooltip.svelte";
import {useI18n} from "$state/i18n.svelte";

let {sessionInfo, user = {}} = $props();
let {session, user = {}} = $props();

let t = useI18n();

Expand Down Expand Up @@ -59,7 +59,7 @@
}

async function fetchPasskeys() {
let res = await getUserPasskeys(sessionInfo.user_id);
let res = await getUserPasskeys(session.user_id);
let body = await res.json();
if (res.ok) {
passkeys = body;
Expand Down
67 changes: 31 additions & 36 deletions frontend/src/components/account/AccMain.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script>
<script lang="ts">
import { run } from 'svelte/legacy';
import {getAuthProvidersTemplate, redirectToLogout} from "../../utils/helpers";
import {redirectToLogout} from "../../utils/helpers";
import AccInfo from "./AccInfo.svelte";
import AccNav from "./AccNav.svelte";
import AccEdit from "./AccEdit.svelte";
Expand All @@ -9,30 +9,33 @@
import LangSelector from "$lib5/LangSelector.svelte";
import AccPassword from "./AccPassword.svelte";
import AccWebId from "./AccWebId.svelte";
import {onMount} from "svelte";
import AccDevices from "./AccDevices.svelte";
import {useI18n} from "$state/i18n.svelte";
import type {SessionResponse} from "$api/response/common/session.js";
import type {UserResponse} from "$api/response/common/user.ts";
import {TPL_AUTH_PROVIDERS} from "../../utils/constants";
import Template from "$lib5/Template.svelte";
import type {AuthProvidersTemplate} from "$api/templates/AuthProvider.ts";

/**
* @typedef {Object} Props
* @property {any} [sessionInfo]
* @property {any} [user]
* @property {any} webIdData - webIdData will stay undefined if it is not enabled in the backend
*/

/** @type {Props} */
let {
sessionInfo,
user = $bindable({}),
session: session,
user = $bindable(),
webIdData = $bindable()
}: {
session: SessionResponse,
user: UserResponse,
webIdData: any,
} = $props();

let t = useI18n();

let innerWidth = $state();
let providers = $state();
let authProvider = $state();

let innerWidth: undefined | number = $state();
let providers: AuthProvidersTemplate = $state([]);
let authProvider = $derived.by(() => {
if (user.account_type?.startsWith('federated')) {
return providers.filter(p => p.id === user.auth_provider_id)[0];
}
});

let op = tweened(1.0, {
duration: 100,
Expand All @@ -41,37 +44,29 @@
let content = $state(t.account.navInfo);
let selected = $state(t.account.navInfo);

onMount(async () => {
providers = await getAuthProvidersTemplate();
});

function animate() {
op.set(0)
.then(() => content = selected)
.then(() => op.set(1.0));
}
let viewModePhone = $derived(innerWidth < 500);
let viewModePhone = $derived(innerWidth && innerWidth < 500);
run(() => {
if (selected) {
animate();
}
});
run(() => {

$effect(() => {
if (selected === t.account.navLogout) {
redirectToLogout();
}
});
run(() => {
if (providers) {
if (user.account_type?.startsWith('federated')) {
authProvider = providers.filter(p => p.id === user.auth_provider_id)[0];
}
}
});
</script>

<svelte:window bind:innerWidth/>

<Template id={TPL_AUTH_PROVIDERS} bind:value={providers}/>

{#if viewModePhone}
<div class="wrapper">
<LangSelector absolute absoluteRight updateBackend/>
Expand All @@ -86,17 +81,17 @@
<div class="innerPhone">
<div style="opacity: {$op}">
{#if content === t.account.navInfo}
<AccInfo bind:user {webIdData} viewModePhone {authProvider}/>
<AccInfo bind:user {webIdData} viewModePhone {providers} {authProvider}/>
{:else if content === t.account.navEdit}
<AccEdit bind:user viewModePhone/>
{:else if content === t.common.password}
<AccPassword {user} {authProvider} viewModePhone/>
{:else if content === t.account.navMfa}
<AccMFA {sessionInfo} {user}/>
<AccMFA {session} {user}/>
{:else if content === 'WebID'}
<AccWebId bind:webIdData />
{:else if content === t.account.devices}
<AccDevices bind:sessionInfo/>
<AccDevices bind:session/>
{/if}
</div>
</div>
Expand All @@ -116,17 +111,17 @@
<div class="inner">
<div style="opacity: {$op}">
{#if content === t.account.navInfo}
<AccInfo bind:user {webIdData} {authProvider}/>
<AccInfo bind:user {webIdData} {providers} {authProvider}/>
{:else if content === t.account.navEdit}
<AccEdit bind:user/>
{:else if content === t.common.password}
<AccPassword {user} {authProvider}/>
{:else if content === t.account.navMfa}
<AccMFA {sessionInfo} {user}/>
<AccMFA {session} {user}/>
{:else if content === 'WebID'}
<AccWebId bind:webIdData/>
{:else if content === t.account.devices}
<AccDevices bind:sessionInfo/>
<AccDevices bind:session/>
{/if}
</div>
</div>
Expand Down
Loading