@vonall/auth 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +22 -0
  3. package/dist/components/ui/avatar/avatar-fallback.svelte +17 -0
  4. package/dist/components/ui/avatar/avatar-fallback.svelte.d.ts +4 -0
  5. package/dist/components/ui/avatar/avatar-image.svelte +17 -0
  6. package/dist/components/ui/avatar/avatar-image.svelte.d.ts +4 -0
  7. package/dist/components/ui/avatar/avatar.svelte +17 -0
  8. package/dist/components/ui/avatar/avatar.svelte.d.ts +4 -0
  9. package/dist/components/ui/avatar/index.d.ts +4 -0
  10. package/dist/components/ui/avatar/index.js +6 -0
  11. package/dist/components/ui/button/button.svelte +82 -0
  12. package/dist/components/ui/button/button.svelte.d.ts +70 -0
  13. package/dist/components/ui/button/index.d.ts +2 -0
  14. package/dist/components/ui/button/index.js +2 -0
  15. package/dist/components/ui/card/card-action.svelte +20 -0
  16. package/dist/components/ui/card/card-action.svelte.d.ts +5 -0
  17. package/dist/components/ui/card/card-content.svelte +15 -0
  18. package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
  19. package/dist/components/ui/card/card-description.svelte +20 -0
  20. package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
  21. package/dist/components/ui/card/card-footer.svelte +20 -0
  22. package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
  23. package/dist/components/ui/card/card-header.svelte +23 -0
  24. package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
  25. package/dist/components/ui/card/card-title.svelte +20 -0
  26. package/dist/components/ui/card/card-title.svelte.d.ts +5 -0
  27. package/dist/components/ui/card/card.svelte +23 -0
  28. package/dist/components/ui/card/card.svelte.d.ts +5 -0
  29. package/dist/components/ui/card/index.d.ts +8 -0
  30. package/dist/components/ui/card/index.js +10 -0
  31. package/dist/components/ui/input/index.d.ts +2 -0
  32. package/dist/components/ui/input/index.js +4 -0
  33. package/dist/components/ui/input/input.svelte +48 -0
  34. package/dist/components/ui/input/input.svelte.d.ts +13 -0
  35. package/dist/components/ui/label/index.d.ts +2 -0
  36. package/dist/components/ui/label/index.js +4 -0
  37. package/dist/components/ui/label/label.svelte +20 -0
  38. package/dist/components/ui/label/label.svelte.d.ts +4 -0
  39. package/dist/components/ui/tabs/index.d.ts +5 -0
  40. package/dist/components/ui/tabs/index.js +7 -0
  41. package/dist/components/ui/tabs/tabs-content.svelte +17 -0
  42. package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
  43. package/dist/components/ui/tabs/tabs-list.svelte +20 -0
  44. package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
  45. package/dist/components/ui/tabs/tabs-trigger.svelte +20 -0
  46. package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
  47. package/dist/components/ui/tabs/tabs.svelte +19 -0
  48. package/dist/components/ui/tabs/tabs.svelte.d.ts +4 -0
  49. package/dist/messages.d.ts +197 -0
  50. package/dist/messages.js +406 -0
  51. package/dist/ui/AdminView.svelte +91 -0
  52. package/dist/ui/AdminView.svelte.d.ts +6 -0
  53. package/dist/ui/ChangePasswordCard.svelte +62 -0
  54. package/dist/ui/ChangePasswordCard.svelte.d.ts +3 -0
  55. package/dist/ui/DeletionTab.svelte +77 -0
  56. package/dist/ui/DeletionTab.svelte.d.ts +6 -0
  57. package/dist/ui/EinladungErstellen.svelte +74 -0
  58. package/dist/ui/EinladungErstellen.svelte.d.ts +3 -0
  59. package/dist/ui/EinladungPage.svelte +42 -0
  60. package/dist/ui/EinladungPage.svelte.d.ts +13 -0
  61. package/dist/ui/IconGithub.svelte +23 -0
  62. package/dist/ui/IconGithub.svelte.d.ts +4 -0
  63. package/dist/ui/IconGoogle.svelte +16 -0
  64. package/dist/ui/IconGoogle.svelte.d.ts +4 -0
  65. package/dist/ui/InvitationsTab.svelte +46 -0
  66. package/dist/ui/InvitationsTab.svelte.d.ts +3 -0
  67. package/dist/ui/LinksTab.svelte +91 -0
  68. package/dist/ui/LinksTab.svelte.d.ts +3 -0
  69. package/dist/ui/LoginForm.svelte +223 -0
  70. package/dist/ui/LoginForm.svelte.d.ts +8 -0
  71. package/dist/ui/MobileSettingsMenu.svelte +37 -0
  72. package/dist/ui/MobileSettingsMenu.svelte.d.ts +11 -0
  73. package/dist/ui/ProfileManagementTab.svelte +56 -0
  74. package/dist/ui/ProfileManagementTab.svelte.d.ts +7 -0
  75. package/dist/ui/RegisterForm.svelte +143 -0
  76. package/dist/ui/RegisterForm.svelte.d.ts +6 -0
  77. package/dist/ui/ResetPasswordForm.svelte +91 -0
  78. package/dist/ui/ResetPasswordForm.svelte.d.ts +3 -0
  79. package/dist/ui/SecurityTab.svelte +25 -0
  80. package/dist/ui/SecurityTab.svelte.d.ts +3 -0
  81. package/dist/ui/SessionsTab.svelte +98 -0
  82. package/dist/ui/SessionsTab.svelte.d.ts +3 -0
  83. package/dist/ui/SetPasswordCard.svelte +56 -0
  84. package/dist/ui/SetPasswordCard.svelte.d.ts +6 -0
  85. package/dist/ui/SettingsHeader.svelte +19 -0
  86. package/dist/ui/SettingsHeader.svelte.d.ts +7 -0
  87. package/dist/ui/SettingsPage.svelte +95 -0
  88. package/dist/ui/SettingsPage.svelte.d.ts +9 -0
  89. package/dist/ui/SettingsTabsList.svelte +17 -0
  90. package/dist/ui/SettingsTabsList.svelte.d.ts +7 -0
  91. package/dist/ui/SocialButton.svelte +22 -0
  92. package/dist/ui/SocialButton.svelte.d.ts +7 -0
  93. package/dist/ui/TwoFactorAuthenticationCard.svelte +220 -0
  94. package/dist/ui/TwoFactorAuthenticationCard.svelte.d.ts +3 -0
  95. package/dist/ui/Verify2FAForm.svelte +63 -0
  96. package/dist/ui/Verify2FAForm.svelte.d.ts +11 -0
  97. package/dist/ui/context.d.ts +93 -0
  98. package/dist/ui/context.js +17 -0
  99. package/dist/ui/href.d.ts +2 -0
  100. package/dist/ui/href.js +5 -0
  101. package/dist/ui/index.d.ts +10 -0
  102. package/dist/ui/index.js +11 -0
  103. package/dist/ui/tabs.d.ts +17 -0
  104. package/dist/ui/tabs.js +21 -0
  105. package/dist/util/fresh.js +1 -1
  106. package/dist/utils.d.ts +12 -0
  107. package/dist/utils.js +6 -0
  108. package/package.json +18 -3
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import * as Tabs from "../components/ui/tabs/index.js";
3
+ import type { SettingsTabDef } from "./tabs.js";
4
+
5
+ let { tabs }: { tabs: SettingsTabDef[] } = $props();
6
+ </script>
7
+
8
+ <!-- Labels at every width: where they don't fit, the bar scrolls sideways (icons alone were not found); w-0 min-w-full
9
+ keeps its labels from widening the page -->
10
+ <Tabs.List class="flex h-12 w-0 min-w-full justify-start overflow-x-auto bg-muted p-1">
11
+ {#each tabs as tab (tab.id)}
12
+ <Tabs.Trigger value={tab.id} class="flex-1 shrink-0 px-3 data-[state=active]:bg-background {tab.id === 'deletion' ? 'text-destructive' : ''}">
13
+ <tab.icon class="size-4" />
14
+ <span>{tab.label}</span>
15
+ </Tabs.Trigger>
16
+ {/each}
17
+ </Tabs.List>
@@ -0,0 +1,7 @@
1
+ import type { SettingsTabDef } from "./tabs.js";
2
+ type $$ComponentProps = {
3
+ tabs: SettingsTabDef[];
4
+ };
5
+ declare const SettingsTabsList: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type SettingsTabsList = ReturnType<typeof SettingsTabsList>;
7
+ export default SettingsTabsList;
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import { Button } from "../components/ui/button/index.js";
3
+ import { m } from "../messages.js";
4
+ import { authUi } from "./context.js";
5
+ import { href } from "./href.js";
6
+ import IconGithub from "./IconGithub.svelte";
7
+ import IconGoogle from "./IconGoogle.svelte";
8
+
9
+ /** "Continue with GitHub/Google": starts the social sign-in with the Product's client. */
10
+ let { provider, callbackURL = href("/home") }: { provider: "github" | "google"; callbackURL?: string } = $props();
11
+ const { authClient } = authUi();
12
+ const names = { github: "GitHub", google: "Google" };
13
+
14
+ async function login() {
15
+ await authClient.signIn.social({ provider, callbackURL, errorCallbackURL: href("/auth/login") });
16
+ }
17
+ </script>
18
+
19
+ <Button variant="outline" class="w-full cursor-pointer" onclick={login}>
20
+ {#if provider === "github"}<IconGithub />{:else}<IconGoogle />{/if}
21
+ {m().continueWith(names[provider])}
22
+ </Button>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ provider: "github" | "google";
3
+ callbackURL?: string;
4
+ };
5
+ declare const SocialButton: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type SocialButton = ReturnType<typeof SocialButton>;
7
+ export default SocialButton;
@@ -0,0 +1,220 @@
1
+ <script lang="ts">
2
+ import { Check, Copy, Shield, ShieldAlert, ShieldCheck } from "@lucide/svelte";
3
+ import { page } from "$app/state";
4
+ import { toast } from "svelte-sonner";
5
+ import { renderSVG } from "uqr";
6
+ import { Button } from "../components/ui/button/index.js";
7
+ import { Input } from "../components/ui/input/index.js";
8
+ import { Label } from "../components/ui/label/index.js";
9
+ import { m } from "../messages.js";
10
+ import { authUi } from "./context.js";
11
+ import { messageOf } from "../util/messageOf.js";
12
+
13
+ const { authClient } = authUi();
14
+ const user = $derived(page.data.user as { twoFactorEnabled?: boolean | null } | undefined);
15
+
16
+ let twoFAInputPassword = $state("");
17
+ let twoFATOTPCode = $state("");
18
+ let isEnabling2FA = $state(false);
19
+ let isRequestingEnable = $state(false);
20
+ let isVerifyingTotp = $state(false);
21
+ let isDisabling2FA = $state(false);
22
+
23
+ let totpURI = $state("");
24
+ let backupCodes = $state<string[]>([]);
25
+ let isCopied = $state(false);
26
+ // Rendered here: the TOTP secret must never leave the browser for a QR service
27
+ const qrSrc = $derived(totpURI ? `data:image/svg+xml;utf8,${encodeURIComponent(renderSVG(totpURI))}` : "");
28
+
29
+ async function handleStartEnable() {
30
+ if (!twoFAInputPassword) {
31
+ toast.error(m().enterPasswordOn);
32
+ return;
33
+ }
34
+ isRequestingEnable = true;
35
+ try {
36
+ const res = await authClient.twoFactor.enable({ password: twoFAInputPassword });
37
+ const data = res.data as { method?: string; totpURI?: string; backupCodes?: string[] } | null | undefined;
38
+ if (res.error) {
39
+ toast.error(res.error.message || m().twoFactorNotOn);
40
+ } else if (data && data.method === "totp" && data.totpURI) {
41
+ totpURI = data.totpURI;
42
+ backupCodes = data.backupCodes || [];
43
+ isEnabling2FA = true;
44
+ toast.success(m().twoFactorEnterCode);
45
+ }
46
+ } catch (e) {
47
+ toast.error(messageOf(e, m().setupNotStarted));
48
+ } finally {
49
+ isRequestingEnable = false;
50
+ }
51
+ }
52
+
53
+ async function handleVerifyTotp() {
54
+ if (!twoFATOTPCode || twoFATOTPCode.length < 6) {
55
+ toast.error(m().invalidCode);
56
+ return;
57
+ }
58
+ isVerifyingTotp = true;
59
+ try {
60
+ const res = await authClient.twoFactor.verifyTotp({ code: twoFATOTPCode, trustDevice: true });
61
+ if (res.error) {
62
+ toast.error(res.error.message || m().wrongCode);
63
+ } else {
64
+ toast.success(m().twoFactorIsOn);
65
+ isEnabling2FA = false;
66
+ twoFAInputPassword = "";
67
+ twoFATOTPCode = "";
68
+ totpURI = "";
69
+ window.location.reload();
70
+ }
71
+ } catch (e) {
72
+ toast.error(messageOf(e, m().codeNotVerified));
73
+ } finally {
74
+ isVerifyingTotp = false;
75
+ }
76
+ }
77
+
78
+ async function handleDisable2FA() {
79
+ if (!twoFAInputPassword) {
80
+ toast.error(m().enterPasswordOff);
81
+ return;
82
+ }
83
+ isRequestingEnable = true;
84
+ try {
85
+ const res = await authClient.twoFactor.disable({ password: twoFAInputPassword });
86
+ if (res.error) {
87
+ toast.error(res.error.message || m().twoFactorNotOff);
88
+ } else {
89
+ toast.success(m().twoFactorIsOff);
90
+ isDisabling2FA = false;
91
+ twoFAInputPassword = "";
92
+ window.location.reload();
93
+ }
94
+ } catch (e) {
95
+ toast.error(messageOf(e, m().twoFactorNotOff));
96
+ } finally {
97
+ isRequestingEnable = false;
98
+ }
99
+ }
100
+
101
+ async function handleCopyBackupCodes() {
102
+ try {
103
+ await navigator.clipboard.writeText(backupCodes.join("\n"));
104
+ isCopied = true;
105
+ toast.success(m().backupCodesCopied);
106
+ setTimeout(() => (isCopied = false), 2000);
107
+ } catch {
108
+ toast.error(m().copyLinkFailed);
109
+ }
110
+ }
111
+ </script>
112
+
113
+ <div class="panel space-y-6 p-6 md:p-8">
114
+ <div class="flex items-center justify-between border-b pb-4">
115
+ <div>
116
+ <h3 class="flex items-center gap-2 text-lg font-semibold">
117
+ <Shield class="size-5 text-muted-foreground" />
118
+ {m().twoFactorHeading}
119
+ </h3>
120
+ <p class="mt-1 text-sm text-muted-foreground">{m().twoFactorIntro}</p>
121
+ </div>
122
+ <div>
123
+ {#if user?.twoFactorEnabled}
124
+ <span class="inline-flex items-center gap-1.5 rounded-full border border-emerald-500/20 bg-emerald-500/10 px-3 py-1.5 text-xs font-semibold text-emerald-600 dark:text-emerald-400">
125
+ <ShieldCheck class="size-4" />
126
+ {m().on}
127
+ </span>
128
+ {:else}
129
+ <span class="inline-flex items-center gap-1.5 rounded-full border border-amber-500/20 bg-amber-500/10 px-3 py-1.5 text-xs font-semibold text-amber-600 dark:text-amber-400">
130
+ <ShieldAlert class="size-4" />
131
+ {m().off}
132
+ </span>
133
+ {/if}
134
+ </div>
135
+ </div>
136
+
137
+ {#if user?.twoFactorEnabled}
138
+ <div class="space-y-4">
139
+ <p class="text-sm text-muted-foreground">{m().twoFactorProtects}</p>
140
+ {#if isDisabling2FA}
141
+ <div class="space-y-4 border-t pt-4">
142
+ <div class="space-y-2">
143
+ <Label for="disable2faPassword" class="text-sm font-medium">{m().yourPassword}</Label>
144
+ <Input id="disable2faPassword" type="password" bind:value={twoFAInputPassword} placeholder="••••••••" class="max-w-md bg-muted/30" />
145
+ </div>
146
+ <div class="flex gap-3">
147
+ <Button variant="destructive" onclick={handleDisable2FA} disabled={isRequestingEnable} class="cursor-pointer">
148
+ {isRequestingEnable ? m().turningOff : m().turnOff}
149
+ </Button>
150
+ <Button
151
+ variant="outline"
152
+ onclick={() => {
153
+ isDisabling2FA = false;
154
+ twoFAInputPassword = "";
155
+ }}
156
+ class="cursor-pointer"
157
+ >
158
+ {m().cancel}
159
+ </Button>
160
+ </div>
161
+ </div>
162
+ {:else}
163
+ <Button variant="destructive" onclick={() => (isDisabling2FA = true)} class="cursor-pointer">{m().turnOff2fa}</Button>
164
+ {/if}
165
+ </div>
166
+ {:else if isEnabling2FA}
167
+ <div class="grid grid-cols-1 gap-8 pt-4 md:grid-cols-2">
168
+ <div class="flex flex-col items-center justify-center space-y-4 rounded-xl border border-dashed bg-muted/20 p-4 text-center">
169
+ <p class="text-xs font-semibold tracking-wider text-muted-foreground uppercase">{m().scanQr}</p>
170
+ <img src={qrSrc} width="180" height="180" alt={m().qrAlt} class="rounded-lg border bg-white p-2 shadow-xs" />
171
+ <p class="max-w-[220px] text-xs text-muted-foreground">{m().scanWithApp}</p>
172
+ </div>
173
+ <div class="space-y-6">
174
+ <div>
175
+ <h4 class="mb-2 text-sm font-semibold">{m().saveBackupCodes}</h4>
176
+ <p class="mb-3 text-xs text-muted-foreground">{m().backupCodesIntro}</p>
177
+ <div class="flex items-center gap-2">
178
+ <code class="block flex-1 truncate rounded-lg border bg-muted/60 p-2.5 font-mono text-xs select-all">{backupCodes.slice(0, 3).join(", ")}...</code>
179
+ <Button variant="outline" size="icon" onclick={handleCopyBackupCodes} title={m().copyBackupCodes} class="cursor-pointer">
180
+ {#if isCopied}<Check class="size-4 text-emerald-500" />{:else}<Copy class="size-4" />{/if}
181
+ </Button>
182
+ </div>
183
+ </div>
184
+
185
+ <div class="space-y-3">
186
+ <h4 class="text-sm font-semibold">{m().confirmSetup}</h4>
187
+ <div class="space-y-2">
188
+ <Label for="totpCode" class="text-sm font-medium">{m().enterCodeFromApp}</Label>
189
+ <Input id="totpCode" type="text" maxlength={6} bind:value={twoFATOTPCode} placeholder="123456" class="max-w-sm bg-muted/30 text-center text-lg font-bold tracking-widest" />
190
+ </div>
191
+ <div class="flex gap-3">
192
+ <Button onclick={handleVerifyTotp} disabled={isVerifyingTotp} class="cursor-pointer">{isVerifyingTotp ? m().checking : m().turnOn2fa}</Button>
193
+ <Button
194
+ variant="outline"
195
+ onclick={() => {
196
+ isEnabling2FA = false;
197
+ twoFAInputPassword = "";
198
+ totpURI = "";
199
+ }}
200
+ class="cursor-pointer"
201
+ >
202
+ {m().cancel}
203
+ </Button>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </div>
208
+ {:else}
209
+ <div class="space-y-4">
210
+ <p class="text-sm text-muted-foreground">{m().setup2faIntro}</p>
211
+ <div class="space-y-4">
212
+ <div class="space-y-2">
213
+ <Label for="enable2faPassword" class="text-sm font-medium">{m().yourPassword}</Label>
214
+ <Input id="enable2faPassword" type="password" bind:value={twoFAInputPassword} placeholder="••••••••" class="max-w-md bg-muted/30" />
215
+ </div>
216
+ <Button onclick={handleStartEnable} disabled={isRequestingEnable} class="cursor-pointer">{isRequestingEnable ? m().starting : m().start2faSetup}</Button>
217
+ </div>
218
+ </div>
219
+ {/if}
220
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const TwoFactorAuthenticationCard: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type TwoFactorAuthenticationCard = ReturnType<typeof TwoFactorAuthenticationCard>;
3
+ export default TwoFactorAuthenticationCard;
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ import { LoaderCircle } from "@lucide/svelte";
3
+ import { Button } from "../components/ui/button/index.js";
4
+ import { Input } from "../components/ui/input/index.js";
5
+ import { Label } from "../components/ui/label/index.js";
6
+ import { m } from "../messages.js";
7
+ import { authUi } from "./context.js";
8
+ import { messageOf } from "../util/messageOf.js";
9
+
10
+ type Props = {
11
+ /** Gets the response data: during an MCP client authorisation it carries the next URL, which the auth client follows. */
12
+ onSuccess: (data?: { redirect?: boolean; url?: string } | null) => void;
13
+ onCancel: () => void;
14
+ };
15
+
16
+ let { onSuccess, onCancel }: Props = $props();
17
+ const { authClient } = authUi();
18
+
19
+ let totpCode = $state("");
20
+ let isVerifyingTotp = $state(false);
21
+ let errorMessage = $state("");
22
+
23
+ async function verify2FA(e: SubmitEvent) {
24
+ e.preventDefault();
25
+ if (!totpCode || totpCode.length < 6) {
26
+ errorMessage = m().invalidCode;
27
+ return;
28
+ }
29
+ isVerifyingTotp = true;
30
+ errorMessage = "";
31
+ try {
32
+ const res = await authClient.twoFactor.verifyTotp({ code: totpCode });
33
+ if (res.error) errorMessage = res.error.message || m().wrongCode;
34
+ else onSuccess(res.data as { redirect?: boolean; url?: string } | null);
35
+ } catch (err) {
36
+ errorMessage = messageOf(err, m().codeNotVerified);
37
+ } finally {
38
+ isVerifyingTotp = false;
39
+ }
40
+ }
41
+ </script>
42
+
43
+ <form onsubmit={verify2FA}>
44
+ <div class="grid gap-6">
45
+ <div class="space-y-2 text-center">
46
+ <Label class="text-sm font-medium">{m().twoFactorTitle}</Label>
47
+ <p class="text-xs text-muted-foreground">{m().twoFactorEnterCode}</p>
48
+ </div>
49
+ <div class="grid gap-2">
50
+ <Input id="totpCode" type="text" maxlength={6} bind:value={totpCode} placeholder="123456" required class="bg-muted/30 text-center text-lg font-bold tracking-widest" autocomplete="one-time-code" />
51
+ </div>
52
+ {#if errorMessage}
53
+ <div class="rounded-md text-center text-sm text-destructive">{errorMessage}</div>
54
+ {/if}
55
+ <div class="flex flex-col gap-2">
56
+ <Button type="submit" class="w-full cursor-pointer" disabled={isVerifyingTotp}>
57
+ {#if isVerifyingTotp}<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />{/if}
58
+ {m().confirm}
59
+ </Button>
60
+ <Button variant="outline" class="w-full cursor-pointer" onclick={onCancel}>{m().cancel}</Button>
61
+ </div>
62
+ </div>
63
+ </form>
@@ -0,0 +1,11 @@
1
+ type Props = {
2
+ /** Gets the response data: during an MCP client authorisation it carries the next URL, which the auth client follows. */
3
+ onSuccess: (data?: {
4
+ redirect?: boolean;
5
+ url?: string;
6
+ } | null) => void;
7
+ onCancel: () => void;
8
+ };
9
+ declare const Verify2FAForm: import("svelte").Component<Props, {}, "">;
10
+ type Verify2FAForm = ReturnType<typeof Verify2FAForm>;
11
+ export default Verify2FAForm;
@@ -0,0 +1,93 @@
1
+ import type { SignupMode } from "../types.js";
2
+ type Outcome<T = unknown> = Promise<{
3
+ data?: T | null;
4
+ error?: {
5
+ message?: string;
6
+ code?: string;
7
+ status?: number;
8
+ } | null;
9
+ }>;
10
+ type FetchCallbacks = {
11
+ onRequest?: () => void;
12
+ onSuccess?: (ctx: {
13
+ data: unknown;
14
+ }) => void | Promise<void>;
15
+ onError?: (ctx: {
16
+ error: {
17
+ code?: string;
18
+ message?: string;
19
+ status?: number;
20
+ };
21
+ }) => void | Promise<void>;
22
+ };
23
+ /**
24
+ * The better-auth client as these components use it (method syntax, so the Product's client with its exact
25
+ * parameter types is assignable).
26
+ */
27
+ export type AuthClientLike = {
28
+ signIn: {
29
+ email(body: {
30
+ email: string;
31
+ password: string;
32
+ }, callbacks?: FetchCallbacks): Outcome;
33
+ social(body: {
34
+ provider: string;
35
+ callbackURL?: string;
36
+ errorCallbackURL?: string;
37
+ }): Outcome;
38
+ };
39
+ signUp: {
40
+ email(body: {
41
+ email: string;
42
+ password: string;
43
+ name: string;
44
+ callbackURL?: string;
45
+ }, callbacks?: FetchCallbacks): Outcome;
46
+ };
47
+ signOut(options?: {
48
+ fetchOptions?: FetchCallbacks;
49
+ }): Outcome;
50
+ requestPasswordReset(body: {
51
+ email: string;
52
+ redirectTo?: string;
53
+ }): Outcome;
54
+ resetPassword(body: {
55
+ newPassword: string;
56
+ token: string;
57
+ }): Outcome;
58
+ twoFactor: {
59
+ verifyTotp(body: {
60
+ code: string;
61
+ trustDevice?: boolean;
62
+ }): Outcome;
63
+ enable(body: {
64
+ password: string;
65
+ }): Outcome<{
66
+ method?: string;
67
+ totpURI?: string;
68
+ backupCodes?: string[];
69
+ } | Record<string, unknown>>;
70
+ disable(body: {
71
+ password: string;
72
+ }): Outcome;
73
+ };
74
+ linkSocial(body: {
75
+ provider: string;
76
+ callbackURL?: string;
77
+ }): Outcome;
78
+ unlinkAccount(body: {
79
+ accountId: string;
80
+ providerId?: string;
81
+ }): Outcome;
82
+ };
83
+ export type AuthUi = {
84
+ authClient: AuthClientLike;
85
+ appName: string;
86
+ /** For dates, e.g. "de-CH". */
87
+ locale: string;
88
+ signupMode: SignupMode;
89
+ };
90
+ /** In the Product's root layout. */
91
+ export declare function setAuthUi(ui: AuthUi): AuthUi;
92
+ export declare function authUi(): AuthUi;
93
+ export {};
@@ -0,0 +1,17 @@
1
+ /**
2
+ * What the auth components need from the Product, set once in its root layout (`setAuthUi({ … })` during component
3
+ * init): the better-auth client (the Product builds it, see `src/lib/auth-client.ts`), the app's name, its locale and
4
+ * signup mode.
5
+ */
6
+ import { getContext, setContext } from "svelte";
7
+ const KEY = Symbol("vonall-auth-ui");
8
+ /** In the Product's root layout. */
9
+ export function setAuthUi(ui) {
10
+ return setContext(KEY, ui);
11
+ }
12
+ export function authUi() {
13
+ const ui = getContext(KEY);
14
+ if (!ui)
15
+ throw new Error("@vonall/auth: setAuthUi({ authClient, appName, locale, signupMode }) has not run in the root layout");
16
+ return ui;
17
+ }
@@ -0,0 +1,2 @@
1
+ /** A link to one of the Product's paths, with SvelteKit's base path (this package has no route ids to type `resolve` against). */
2
+ export declare function href(path: string): string;
@@ -0,0 +1,5 @@
1
+ import { resolve } from "$app/paths";
2
+ /** A link to one of the Product's paths, with SvelteKit's base path (this package has no route ids to type `resolve` against). */
3
+ export function href(path) {
4
+ return resolve(path);
5
+ }
@@ -0,0 +1,10 @@
1
+ export { default as AdminView } from "./AdminView.svelte";
2
+ export { authUi, setAuthUi, type AuthClientLike, type AuthUi } from "./context.js";
3
+ export { default as EinladungErstellen } from "./EinladungErstellen.svelte";
4
+ export { default as EinladungPage } from "./EinladungPage.svelte";
5
+ export { default as LoginForm } from "./LoginForm.svelte";
6
+ export { default as RegisterForm } from "./RegisterForm.svelte";
7
+ export { default as ResetPasswordForm } from "./ResetPasswordForm.svelte";
8
+ export { default as SettingsPage } from "./SettingsPage.svelte";
9
+ export { default as SocialButton } from "./SocialButton.svelte";
10
+ export { deletionTab, settingsTabs, visibleTabs, type SettingsTabDef } from "./tabs.js";
@@ -0,0 +1,11 @@
1
+ // The pages and the settings of accounts, for a Product's routes; `setAuthUi({ … })` in its root layout first
2
+ export { default as AdminView } from "./AdminView.svelte";
3
+ export { authUi, setAuthUi } from "./context.js";
4
+ export { default as EinladungErstellen } from "./EinladungErstellen.svelte";
5
+ export { default as EinladungPage } from "./EinladungPage.svelte";
6
+ export { default as LoginForm } from "./LoginForm.svelte";
7
+ export { default as RegisterForm } from "./RegisterForm.svelte";
8
+ export { default as ResetPasswordForm } from "./ResetPasswordForm.svelte";
9
+ export { default as SettingsPage } from "./SettingsPage.svelte";
10
+ export { default as SocialButton } from "./SocialButton.svelte";
11
+ export { deletionTab, settingsTabs, visibleTabs } from "./tabs.js";
@@ -0,0 +1,17 @@
1
+ import type { Component } from "svelte";
2
+ /** One settings tab: the desktop tab bar and the mobile menu are built from the list. */
3
+ export type SettingsTabDef = {
4
+ id: string;
5
+ label: string;
6
+ hint: string;
7
+ icon: Component<{
8
+ size?: number | string;
9
+ class?: string;
10
+ }>;
11
+ };
12
+ /** This package's tabs, in order, in the configured language; a Product adds its own (e.g. the AI assistants) through `SettingsPage`. */
13
+ export declare function settingsTabs(): SettingsTabDef[];
14
+ /** The deletion tab comes last, after a Product's own. */
15
+ export declare function deletionTab(): SettingsTabDef;
16
+ /** The tabs this user sees: Invitations only when the signup mode lets them invite (`einladen` in the page data). */
17
+ export declare function visibleTabs(einladen: boolean, extra?: SettingsTabDef[]): SettingsTabDef[];
@@ -0,0 +1,21 @@
1
+ import { Link as LinkIcon, Monitor, Shield, Trash2, User, UserPlus } from "@lucide/svelte";
2
+ import { m } from "../messages.js";
3
+ /** This package's tabs, in order, in the configured language; a Product adds its own (e.g. the AI assistants) through `SettingsPage`. */
4
+ export function settingsTabs() {
5
+ const t = m();
6
+ return [
7
+ { id: "profile", label: t.tabProfile, hint: t.tabProfileHint, icon: User },
8
+ { id: "security", label: t.tabSecurity, hint: t.tabSecurityHint, icon: Shield },
9
+ { id: "sessions", label: t.tabSessions, hint: t.tabSessionsHint, icon: Monitor },
10
+ { id: "links", label: t.tabLinks, hint: t.tabLinksHint, icon: LinkIcon },
11
+ { id: "invitations", label: t.tabInvitations, hint: t.tabInvitationsHint, icon: UserPlus },
12
+ ];
13
+ }
14
+ /** The deletion tab comes last, after a Product's own. */
15
+ export function deletionTab() {
16
+ return { id: "deletion", label: m().tabDeletion, hint: m().tabDeletionHint, icon: Trash2 };
17
+ }
18
+ /** The tabs this user sees: Invitations only when the signup mode lets them invite (`einladen` in the page data). */
19
+ export function visibleTabs(einladen, extra = []) {
20
+ return [...settingsTabs().filter((tab) => tab.id !== "invitations" || einladen), ...extra, deletionTab()];
21
+ }
@@ -12,7 +12,7 @@ export function isSessionNotFresh(e) {
12
12
  }
13
13
  /** The login page that signs out a stale session and then returns to `redirectTo`. */
14
14
  export function reauthPath(redirectTo) {
15
- return `/auth/login?error=${SESSION_NOT_FRESH.toLowerCase()}&redirectTo=${encodeURIComponent(redirectTo)}`;
15
+ return `/auth/login?error=${SESSION_NOT_FRESH.toLowerCase()}&redirectTo=${encodeURIComponent(redirectTo)}`; // the Product's loads prefix the base path
16
16
  }
17
17
  /** better-auth's own check: a `freshAge` of 0 turns it off. */
18
18
  export function isStale(createdAt, freshAgeSeconds, now = new Date()) {
@@ -0,0 +1,12 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;
3
+ export type WithoutChild<T> = T extends {
4
+ child?: any;
5
+ } ? Omit<T, "child"> : T;
6
+ export type WithoutChildren<T> = T extends {
7
+ children?: any;
8
+ } ? Omit<T, "children"> : T;
9
+ export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
10
+ export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & {
11
+ ref?: U | null;
12
+ };
package/dist/utils.js ADDED
@@ -0,0 +1,6 @@
1
+ // Needs to be located in the lib directory to be used by shadcn
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+ export function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vonall/auth",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Accounts for a Web App Factory Product: better-auth with the hooks, the protected helpers, Einladungen, Konto, the auth pages and the settings; Admin as an option",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,20 +44,29 @@
44
44
  "./testing": {
45
45
  "types": "./dist/testing/index.d.ts",
46
46
  "default": "./dist/testing/index.js"
47
+ },
48
+ "./ui": {
49
+ "types": "./dist/ui/index.d.ts",
50
+ "svelte": "./dist/ui/index.js",
51
+ "default": "./dist/ui/index.js"
47
52
  }
48
53
  },
49
54
  "peerDependencies": {
55
+ "@node-rs/argon2": "^2.2.2",
50
56
  "@sveltejs/kit": "^3.1.0",
51
57
  "drizzle-orm": "^0.45.4",
52
58
  "neverthrow": "^8.2.0",
53
59
  "pg": "^8.23.1",
54
60
  "svelte": "^5.57.2",
55
61
  "zod": "^4.6.7",
56
- "@node-rs/argon2": "^2.2.2"
62
+ "svelte-sonner": "^1.2.1",
63
+ "bits-ui": "^2.19.5",
64
+ "@lucide/svelte": "^1.22.0"
57
65
  },
58
66
  "devDependencies": {
59
67
  "@better-auth/mcp": "^1.7.12",
60
68
  "@better-auth/oauth-provider": "^1.7.12",
69
+ "@node-rs/argon2": "^2.2.2",
61
70
  "@sveltejs/kit": "^3.1.0",
62
71
  "@sveltejs/package": "^3.0.1",
63
72
  "@sveltejs/vite-plugin-svelte": "7.3.1",
@@ -73,7 +82,9 @@
73
82
  "vite": "8.3.4",
74
83
  "vitest": "^5.0.5",
75
84
  "zod": "^4.6.7",
76
- "@node-rs/argon2": "^2.2.2"
85
+ "svelte-sonner": "^1.2.1",
86
+ "bits-ui": "^2.19.5",
87
+ "@lucide/svelte": "^1.22.0"
77
88
  },
78
89
  "imports": {
79
90
  "#lib": "./src/lib/index.js",
@@ -87,6 +98,10 @@
87
98
  },
88
99
  "dependencies": {
89
100
  "better-auth": "^1.7.12",
101
+ "clsx": "^2.1.1",
102
+ "tailwind-merge": "^3.7.0",
103
+ "tailwind-variants": "^3.3.1",
104
+ "uqr": "^0.1.3",
90
105
  "@vonall/mail": "^0.1.1"
91
106
  },
92
107
  "scripts": {