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
10 changes: 10 additions & 0 deletions backend/InTheKnow.Tests/SignInTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,16 @@ public async Task ResetRequests()
Assert.Equal(3, await d.Db.ScalarAsync("SELECT count(*) FROM account_message WHERE kind = 'reset'"));
}

// Passwords take any character: only the length is checked (Passwords.cs).
[Fact]
public async Task AnyCharacterInAPassword()
{
await using var d = await SignUpTests.HostAsync();
const string password = "Zwölf café 🍕 Ωmega 漢字 \"'<&>";
await Expect(await CompleteAsync(d, await ResetTokenAsync(d, d.S1.Email), password), HttpStatusCode.NoContent);
await Expect(await PasswordSignInAsync(d.H, d.S1.Email, password, "198.51.100.1"), HttpStatusCode.OK);
}

// US3-2, US3-3, SC-009.
[Fact]
public async Task CompleteReset()
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/lib/components/Field.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import type { HTMLInputAttributes } from 'svelte/elements';
import { page } from '$app/state';
import Eye from '@lucide/svelte/icons/eye';
import EyeOff from '@lucide/svelte/icons/eye-off';
import { dicts } from '$lib/i18n';

// A labelled input, or a custom control through `children` (which receives the ids to wire up).
type Props = Omit<HTMLInputAttributes, 'children'> & {
Expand All @@ -13,13 +17,22 @@
};
let { label, id, hint, error, value = $bindable(), children, ...rest }: Props = $props();

const t = $derived(dicts[page.data.lang]);
let shown = $state(false);
const describedby = $derived([hint && `${id}-hint`, error && `${id}-error`].filter(Boolean).join(' ') || undefined);
</script>

<div class="field">
<label for={id}>{label}</label>
{#if children}
{@render children({ id, describedby, invalid: Boolean(error) })}
{:else if rest.type === 'password'}
<div class="reveal">
<input {id} bind:value aria-describedby={describedby} aria-invalid={error ? 'true' : undefined} {...rest} type={shown ? 'text' : 'password'} />
<button type="button" aria-label={t.auth.showPassword} aria-pressed={shown} aria-controls={id} onclick={() => (shown = !shown)}>
{#if shown}<EyeOff size={20} aria-hidden="true" />{:else}<Eye size={20} aria-hidden="true" />{/if}
</button>
</div>
{:else}
<input {id} bind:value aria-describedby={describedby} aria-invalid={error ? 'true' : undefined} {...rest} />
{/if}
Expand Down Expand Up @@ -67,6 +80,28 @@
.field :global(input:disabled) {
background: var(--grey-100);
}
.reveal {
position: relative;
}
/* Outweighs the general input rule above, which sets padding on both sides. */
.field .reveal :global(input:not([type='checkbox'])) {
padding-right: 48px;
}
.reveal button {
position: absolute;
inset: 0 0 0 auto;
width: 48px;
display: grid;
place-items: center;
color: var(--text-muted);
background: none;
border: 0;
border-radius: var(--radius-md);
cursor: pointer;
}
.reveal button:hover {
color: var(--text-strong);
}
.hint {
font: var(--type-small);
color: var(--text-muted);
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export const en = {
password: 'Password',
newPassword: 'New password',
language: 'Language',
showPassword: 'Show password',
rulesTitle: 'Your password needs:',
ruleLength: '12 to 128 characters',
ruleCommon: 'not to be a common password',
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/i18n/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export const nl: Dict = {
email: 'E-mailadres',
password: 'Wachtwoord',
newPassword: 'Nieuw wachtwoord',
showPassword: 'Wachtwoord tonen',
language: 'Taal',
rulesTitle: 'Je wachtwoord moet:',
ruleLength: '12 tot 128 tekens lang zijn',
Expand Down
Loading