@authrim/sveltekit 0.1.0 → 0.1.2

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 (34) hide show
  1. package/LICENSE +191 -191
  2. package/README.md +527 -531
  3. package/dist/components/AuthProvider.svelte +56 -56
  4. package/dist/components/ProtectedRoute.svelte +71 -71
  5. package/dist/components/SignInButton.svelte +93 -93
  6. package/dist/components/SignOutButton.svelte +72 -72
  7. package/dist/components/UserProfile.svelte +71 -71
  8. package/dist/ui/account/LinkAccountButton.svelte +133 -133
  9. package/dist/ui/account/LinkedAccountsList.svelte +233 -233
  10. package/dist/ui/account/UnlinkAccountButton.svelte +179 -179
  11. package/dist/ui/forms/EmailCodeForm.svelte +224 -224
  12. package/dist/ui/forms/PasskeyConditionalInput.svelte +173 -173
  13. package/dist/ui/forms/SocialLoginButtons.svelte +209 -209
  14. package/dist/ui/helpers/AuthError.svelte +124 -124
  15. package/dist/ui/helpers/AuthLoading.svelte +83 -83
  16. package/dist/ui/helpers/OTPInput.svelte +214 -214
  17. package/dist/ui/helpers/ResendCodeButton.svelte +140 -140
  18. package/dist/ui/passkey/PasskeyDeleteButton.svelte +177 -177
  19. package/dist/ui/passkey/PasskeyList.svelte +225 -225
  20. package/dist/ui/passkey/PasskeyRegisterButton.svelte +52 -52
  21. package/dist/ui/session/SessionExpiryIndicator.svelte +109 -109
  22. package/dist/ui/session/SessionList.svelte +231 -231
  23. package/dist/ui/session/SessionRevokeButton.svelte +72 -72
  24. package/dist/ui/shared/Badge.svelte +100 -100
  25. package/dist/ui/shared/Button.svelte +213 -213
  26. package/dist/ui/shared/Card.svelte +85 -85
  27. package/dist/ui/shared/Input.svelte +192 -192
  28. package/dist/ui/shared/Spinner.svelte +75 -75
  29. package/dist/ui/styles/base.css +168 -168
  30. package/dist/ui/styles/theme.css +279 -279
  31. package/dist/ui/templates/AccountSettingsTemplate.svelte +205 -205
  32. package/dist/ui/templates/LoginTemplate.svelte +234 -234
  33. package/dist/ui/templates/SignUpTemplate.svelte +345 -345
  34. package/package.json +112 -111
@@ -1,56 +1,56 @@
1
- <!--
2
- AuthProvider Component
3
-
4
- Responsibilities (strictly limited):
5
- - Wire auth instance into Svelte context
6
- - Sync initial session from SSR (synchronously to avoid hydration mismatch)
7
-
8
- It must NOT implement business logic.
9
- -->
10
- <script lang="ts">
11
- import { onMount, onDestroy } from 'svelte';
12
- import { setAuthContext } from '../utils/context.js';
13
- import type { AuthrimClient } from '../types.js';
14
- import type { Session, User } from '@authrim/core';
15
-
16
- /** Authrim client instance */
17
- export let auth: AuthrimClient;
18
-
19
- /** Initial session from SSR (optional) */
20
- export let initialSession: Session | null = null;
21
-
22
- /** Initial user from SSR (optional) */
23
- export let initialUser: User | null = null;
24
-
25
- // Set auth context for child components
26
- setAuthContext(auth);
27
-
28
- // Sync initial session from SSR synchronously to avoid hydration mismatch
29
- // This must happen before the first render
30
- if (initialSession && initialUser) {
31
- auth._syncFromSSR(initialSession, initialUser);
32
- }
33
-
34
- // Optionally validate session in the background after hydration
35
- onMount(() => {
36
- if (initialSession && initialUser) {
37
- // Session already synced above, optionally revalidate in background
38
- // This ensures the session is still valid on the server
39
- auth.session.get().catch((error) => {
40
- console.warn('[Authrim] Failed to revalidate session:', error);
41
- });
42
- } else if (!initialSession && !initialUser) {
43
- // No SSR session provided, check if there's a session in storage
44
- auth.session.get().catch((error) => {
45
- console.warn('[Authrim] Failed to fetch session:', error);
46
- });
47
- }
48
- });
49
-
50
- // Cleanup on destroy
51
- onDestroy(() => {
52
- auth.destroy();
53
- });
54
- </script>
55
-
56
- <slot />
1
+ <!--
2
+ AuthProvider Component
3
+
4
+ Responsibilities (strictly limited):
5
+ - Wire auth instance into Svelte context
6
+ - Sync initial session from SSR (synchronously to avoid hydration mismatch)
7
+
8
+ It must NOT implement business logic.
9
+ -->
10
+ <script lang="ts">
11
+ import { onMount, onDestroy } from 'svelte';
12
+ import { setAuthContext } from '../utils/context.js';
13
+ import type { AuthrimClient } from '../types.js';
14
+ import type { Session, User } from '@authrim/core';
15
+
16
+ /** Authrim client instance */
17
+ export let auth: AuthrimClient;
18
+
19
+ /** Initial session from SSR (optional) */
20
+ export let initialSession: Session | null = null;
21
+
22
+ /** Initial user from SSR (optional) */
23
+ export let initialUser: User | null = null;
24
+
25
+ // Set auth context for child components
26
+ setAuthContext(auth);
27
+
28
+ // Sync initial session from SSR synchronously to avoid hydration mismatch
29
+ // This must happen before the first render
30
+ if (initialSession && initialUser) {
31
+ auth._syncFromSSR(initialSession, initialUser);
32
+ }
33
+
34
+ // Optionally validate session in the background after hydration
35
+ onMount(() => {
36
+ if (initialSession && initialUser) {
37
+ // Session already synced above, optionally revalidate in background
38
+ // This ensures the session is still valid on the server
39
+ auth.session.get().catch((error) => {
40
+ console.warn('[Authrim] Failed to revalidate session:', error);
41
+ });
42
+ } else if (!initialSession && !initialUser) {
43
+ // No SSR session provided, check if there's a session in storage
44
+ auth.session.get().catch((error) => {
45
+ console.warn('[Authrim] Failed to fetch session:', error);
46
+ });
47
+ }
48
+ });
49
+
50
+ // Cleanup on destroy
51
+ onDestroy(() => {
52
+ auth.destroy();
53
+ });
54
+ </script>
55
+
56
+ <slot />
@@ -1,71 +1,71 @@
1
- <!--
2
- ProtectedRoute Component
3
-
4
- Shows content only when user is authenticated.
5
- Redirects or shows fallback when not authenticated.
6
- -->
7
- <script lang="ts">
8
- import { onMount, tick } from 'svelte';
9
- import { goto } from '$app/navigation';
10
- import { getAuthContext } from '../utils/context.js';
11
-
12
- /** Redirect URL for unauthenticated users (if not provided, shows fallback) */
13
- export let redirectTo: string | undefined = undefined;
14
-
15
- /** Include current path in redirect URL */
16
- export let includeReturnPath: boolean = true;
17
-
18
- /** Return path parameter name */
19
- export let returnPathParam: string = 'redirectTo';
20
-
21
- /** Custom class */
22
- let className: string = '';
23
- export { className as class };
24
-
25
- const auth = getAuthContext();
26
- const { isAuthenticated, loadingState } = auth.stores;
27
-
28
- // State machine: 'loading' -> 'ready'
29
- // Prevents race condition where redirect happens before session check completes
30
- let state: 'loading' | 'ready' = 'loading';
31
-
32
- onMount(async () => {
33
- try {
34
- // Check session on mount
35
- await auth.session.get();
36
- } catch (error) {
37
- console.warn('[Authrim] Failed to check session:', error);
38
- } finally {
39
- // Wait for stores to update before changing state
40
- await tick();
41
- state = 'ready';
42
- }
43
- });
44
-
45
- // Only perform redirect when:
46
- // 1. Component has finished initialization (state === 'ready')
47
- // 2. User is not authenticated
48
- // 3. No async operation is in progress (loadingState === 'idle')
49
- // 4. A redirect URL is configured
50
- $: if (state === 'ready' && !$isAuthenticated && $loadingState === 'idle' && redirectTo) {
51
- const url = new URL(redirectTo, window.location.origin);
52
- if (includeReturnPath) {
53
- url.searchParams.set(returnPathParam, window.location.pathname + window.location.search);
54
- }
55
- goto(url.toString());
56
- }
57
- </script>
58
-
59
- {#if state === 'loading' || $loadingState === 'initializing'}
60
- <slot name="loading">
61
- <div class={className} {...$$restProps}>Loading...</div>
62
- </slot>
63
- {:else if $isAuthenticated}
64
- <slot />
65
- {:else if !redirectTo}
66
- <slot name="unauthenticated">
67
- <div class={className} {...$$restProps}>
68
- <p>You must be signed in to view this content.</p>
69
- </div>
70
- </slot>
71
- {/if}
1
+ <!--
2
+ ProtectedRoute Component
3
+
4
+ Shows content only when user is authenticated.
5
+ Redirects or shows fallback when not authenticated.
6
+ -->
7
+ <script lang="ts">
8
+ import { onMount, tick } from 'svelte';
9
+ import { goto } from '$app/navigation';
10
+ import { getAuthContext } from '../utils/context.js';
11
+
12
+ /** Redirect URL for unauthenticated users (if not provided, shows fallback) */
13
+ export let redirectTo: string | undefined = undefined;
14
+
15
+ /** Include current path in redirect URL */
16
+ export let includeReturnPath: boolean = true;
17
+
18
+ /** Return path parameter name */
19
+ export let returnPathParam: string = 'redirectTo';
20
+
21
+ /** Custom class */
22
+ let className: string = '';
23
+ export { className as class };
24
+
25
+ const auth = getAuthContext();
26
+ const { isAuthenticated, loadingState } = auth.stores;
27
+
28
+ // State machine: 'loading' -> 'ready'
29
+ // Prevents race condition where redirect happens before session check completes
30
+ let state: 'loading' | 'ready' = 'loading';
31
+
32
+ onMount(async () => {
33
+ try {
34
+ // Check session on mount
35
+ await auth.session.get();
36
+ } catch (error) {
37
+ console.warn('[Authrim] Failed to check session:', error);
38
+ } finally {
39
+ // Wait for stores to update before changing state
40
+ await tick();
41
+ state = 'ready';
42
+ }
43
+ });
44
+
45
+ // Only perform redirect when:
46
+ // 1. Component has finished initialization (state === 'ready')
47
+ // 2. User is not authenticated
48
+ // 3. No async operation is in progress (loadingState === 'idle')
49
+ // 4. A redirect URL is configured
50
+ $: if (state === 'ready' && !$isAuthenticated && $loadingState === 'idle' && redirectTo) {
51
+ const url = new URL(redirectTo, window.location.origin);
52
+ if (includeReturnPath) {
53
+ url.searchParams.set(returnPathParam, window.location.pathname + window.location.search);
54
+ }
55
+ goto(url.toString());
56
+ }
57
+ </script>
58
+
59
+ {#if state === 'loading' || $loadingState === 'initializing'}
60
+ <slot name="loading">
61
+ <div class={className} {...$$restProps}>Loading...</div>
62
+ </slot>
63
+ {:else if $isAuthenticated}
64
+ <slot />
65
+ {:else if !redirectTo}
66
+ <slot name="unauthenticated">
67
+ <div class={className} {...$$restProps}>
68
+ <p>You must be signed in to view this content.</p>
69
+ </div>
70
+ </slot>
71
+ {/if}
@@ -1,93 +1,93 @@
1
- <!--
2
- SignInButton Component
3
-
4
- A thin wrapper that delegates all behavior to props/events.
5
- Responsibilities: Call auth API and dispatch events.
6
- -->
7
- <script lang="ts">
8
- import { createEventDispatcher } from 'svelte';
9
- import { getAuthContext } from '../utils/context.js';
10
- import type { SocialProvider, Session, User, AuthLoadingState } from '../types.js';
11
- import type { AuthError } from '../stores/auth.js';
12
-
13
- /** Authentication method */
14
- export let method: 'passkey' | 'social' = 'passkey';
15
-
16
- /** Social provider (required when method is 'social') */
17
- export let provider: SocialProvider | undefined = undefined;
18
-
19
- /** Disabled state */
20
- export let disabled: boolean = false;
21
-
22
- /** Custom class */
23
- let className: string = '';
24
- export { className as class };
25
-
26
- const auth = getAuthContext();
27
- const dispatch = createEventDispatcher<{
28
- success: { session: Session; user: User };
29
- error: AuthError;
30
- loading: AuthLoadingState;
31
- }>();
32
-
33
- let loading = false;
34
-
35
- async function handleClick() {
36
- if (disabled || loading) return;
37
-
38
- loading = true;
39
- dispatch('loading', 'authenticating');
40
-
41
- try {
42
- let result;
43
-
44
- if (method === 'passkey') {
45
- result = await auth.passkey.login();
46
- } else if (method === 'social' && provider) {
47
- result = await auth.social.loginWithPopup(provider);
48
- } else {
49
- throw new Error('Invalid method or missing provider');
50
- }
51
-
52
- if (result.data) {
53
- dispatch('success', {
54
- session: result.data.session,
55
- user: result.data.user,
56
- });
57
- } else if (result.error) {
58
- dispatch('error', {
59
- code: result.error.code,
60
- message: result.error.message,
61
- });
62
- }
63
- } catch (error) {
64
- dispatch('error', {
65
- code: 'UNKNOWN_ERROR',
66
- message: error instanceof Error ? error.message : 'Unknown error',
67
- });
68
- } finally {
69
- loading = false;
70
- dispatch('loading', 'idle');
71
- }
72
- }
73
- </script>
74
-
75
- <button
76
- type="button"
77
- on:click={handleClick}
78
- disabled={disabled || loading}
79
- class={className}
80
- {...$$restProps}
81
- >
82
- <slot>
83
- {#if loading}
84
- Signing in...
85
- {:else if method === 'passkey'}
86
- Sign in with Passkey
87
- {:else if method === 'social' && provider}
88
- Sign in with {provider.charAt(0).toUpperCase() + provider.slice(1)}
89
- {:else}
90
- Sign In
91
- {/if}
92
- </slot>
93
- </button>
1
+ <!--
2
+ SignInButton Component
3
+
4
+ A thin wrapper that delegates all behavior to props/events.
5
+ Responsibilities: Call auth API and dispatch events.
6
+ -->
7
+ <script lang="ts">
8
+ import { createEventDispatcher } from 'svelte';
9
+ import { getAuthContext } from '../utils/context.js';
10
+ import type { SocialProvider, Session, User, AuthLoadingState } from '../types.js';
11
+ import type { AuthError } from '../stores/auth.js';
12
+
13
+ /** Authentication method */
14
+ export let method: 'passkey' | 'social' = 'passkey';
15
+
16
+ /** Social provider (required when method is 'social') */
17
+ export let provider: SocialProvider | undefined = undefined;
18
+
19
+ /** Disabled state */
20
+ export let disabled: boolean = false;
21
+
22
+ /** Custom class */
23
+ let className: string = '';
24
+ export { className as class };
25
+
26
+ const auth = getAuthContext();
27
+ const dispatch = createEventDispatcher<{
28
+ success: { session: Session; user: User };
29
+ error: AuthError;
30
+ loading: AuthLoadingState;
31
+ }>();
32
+
33
+ let loading = false;
34
+
35
+ async function handleClick() {
36
+ if (disabled || loading) return;
37
+
38
+ loading = true;
39
+ dispatch('loading', 'authenticating');
40
+
41
+ try {
42
+ let result;
43
+
44
+ if (method === 'passkey') {
45
+ result = await auth.passkey.login();
46
+ } else if (method === 'social' && provider) {
47
+ result = await auth.social.loginWithPopup(provider);
48
+ } else {
49
+ throw new Error('Invalid method or missing provider');
50
+ }
51
+
52
+ if (result.data) {
53
+ dispatch('success', {
54
+ session: result.data.session,
55
+ user: result.data.user,
56
+ });
57
+ } else if (result.error) {
58
+ dispatch('error', {
59
+ code: result.error.code,
60
+ message: result.error.message,
61
+ });
62
+ }
63
+ } catch (error) {
64
+ dispatch('error', {
65
+ code: 'UNKNOWN_ERROR',
66
+ message: error instanceof Error ? error.message : 'Unknown error',
67
+ });
68
+ } finally {
69
+ loading = false;
70
+ dispatch('loading', 'idle');
71
+ }
72
+ }
73
+ </script>
74
+
75
+ <button
76
+ type="button"
77
+ on:click={handleClick}
78
+ disabled={disabled || loading}
79
+ class={className}
80
+ {...$$restProps}
81
+ >
82
+ <slot>
83
+ {#if loading}
84
+ Signing in...
85
+ {:else if method === 'passkey'}
86
+ Sign in with Passkey
87
+ {:else if method === 'social' && provider}
88
+ Sign in with {provider.charAt(0).toUpperCase() + provider.slice(1)}
89
+ {:else}
90
+ Sign In
91
+ {/if}
92
+ </slot>
93
+ </button>
@@ -1,72 +1,72 @@
1
- <!--
2
- SignOutButton Component
3
-
4
- A thin wrapper that delegates all behavior to props/events.
5
- -->
6
- <script lang="ts">
7
- import { createEventDispatcher } from 'svelte';
8
- import { getAuthContext } from '../utils/context.js';
9
- import type { AuthLoadingState } from '../types.js';
10
- import type { AuthError } from '../stores/auth.js';
11
-
12
- /** Redirect URI after sign out */
13
- export let redirectUri: string | undefined = undefined;
14
-
15
- /** Revoke tokens on sign out */
16
- export let revokeTokens: boolean = false;
17
-
18
- /** Disabled state */
19
- export let disabled: boolean = false;
20
-
21
- /** Custom class */
22
- let className: string = '';
23
- export { className as class };
24
-
25
- const auth = getAuthContext();
26
- const dispatch = createEventDispatcher<{
27
- success: void;
28
- error: AuthError;
29
- loading: AuthLoadingState;
30
- }>();
31
-
32
- let loading = false;
33
-
34
- async function handleClick() {
35
- if (disabled || loading) return;
36
-
37
- loading = true;
38
- dispatch('loading', 'signing_out');
39
-
40
- try {
41
- await auth.signOut({
42
- redirectUri,
43
- revokeTokens,
44
- });
45
- dispatch('success');
46
- } catch (error) {
47
- dispatch('error', {
48
- code: 'SIGN_OUT_ERROR',
49
- message: error instanceof Error ? error.message : 'Sign out failed',
50
- });
51
- } finally {
52
- loading = false;
53
- dispatch('loading', 'idle');
54
- }
55
- }
56
- </script>
57
-
58
- <button
59
- type="button"
60
- on:click={handleClick}
61
- disabled={disabled || loading}
62
- class={className}
63
- {...$$restProps}
64
- >
65
- <slot>
66
- {#if loading}
67
- Signing out...
68
- {:else}
69
- Sign Out
70
- {/if}
71
- </slot>
72
- </button>
1
+ <!--
2
+ SignOutButton Component
3
+
4
+ A thin wrapper that delegates all behavior to props/events.
5
+ -->
6
+ <script lang="ts">
7
+ import { createEventDispatcher } from 'svelte';
8
+ import { getAuthContext } from '../utils/context.js';
9
+ import type { AuthLoadingState } from '../types.js';
10
+ import type { AuthError } from '../stores/auth.js';
11
+
12
+ /** Redirect URI after sign out */
13
+ export let redirectUri: string | undefined = undefined;
14
+
15
+ /** Revoke tokens on sign out */
16
+ export let revokeTokens: boolean = false;
17
+
18
+ /** Disabled state */
19
+ export let disabled: boolean = false;
20
+
21
+ /** Custom class */
22
+ let className: string = '';
23
+ export { className as class };
24
+
25
+ const auth = getAuthContext();
26
+ const dispatch = createEventDispatcher<{
27
+ success: void;
28
+ error: AuthError;
29
+ loading: AuthLoadingState;
30
+ }>();
31
+
32
+ let loading = false;
33
+
34
+ async function handleClick() {
35
+ if (disabled || loading) return;
36
+
37
+ loading = true;
38
+ dispatch('loading', 'signing_out');
39
+
40
+ try {
41
+ await auth.signOut({
42
+ redirectUri,
43
+ revokeTokens,
44
+ });
45
+ dispatch('success');
46
+ } catch (error) {
47
+ dispatch('error', {
48
+ code: 'SIGN_OUT_ERROR',
49
+ message: error instanceof Error ? error.message : 'Sign out failed',
50
+ });
51
+ } finally {
52
+ loading = false;
53
+ dispatch('loading', 'idle');
54
+ }
55
+ }
56
+ </script>
57
+
58
+ <button
59
+ type="button"
60
+ on:click={handleClick}
61
+ disabled={disabled || loading}
62
+ class={className}
63
+ {...$$restProps}
64
+ >
65
+ <slot>
66
+ {#if loading}
67
+ Signing out...
68
+ {:else}
69
+ Sign Out
70
+ {/if}
71
+ </slot>
72
+ </button>