@autumnsgrove/groveengine 0.9.95 → 0.9.96
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.
- package/dist/grafts/login/LoginGraft.svelte +115 -45
- package/package.json +1 -1
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
* LoginGraft - Unified login component for all Grove properties
|
|
4
4
|
*
|
|
5
5
|
* Main orchestrator component that provides consistent login UI.
|
|
6
|
-
* Uses Better Auth for OAuth flows - POSTs to GroveAuth's
|
|
6
|
+
* Uses Better Auth for OAuth flows - POSTs JSON to GroveAuth's
|
|
7
7
|
* Better Auth endpoints which handle the full OAuth dance.
|
|
8
8
|
*
|
|
9
|
-
* IMPORTANT: Better Auth's /api/auth/sign-in/social endpoint requires
|
|
10
|
-
*
|
|
9
|
+
* IMPORTANT: Better Auth's /api/auth/sign-in/social endpoint requires:
|
|
10
|
+
* - POST method
|
|
11
|
+
* - Content-Type: application/json
|
|
12
|
+
* - JSON body with { provider, callbackURL }
|
|
11
13
|
*
|
|
12
14
|
* Supports three variants:
|
|
13
15
|
* - default: Card with providers and optional header/footer
|
|
@@ -67,6 +69,10 @@
|
|
|
67
69
|
providers.filter((p) => isProviderAvailable(p))
|
|
68
70
|
);
|
|
69
71
|
|
|
72
|
+
// Loading state for buttons
|
|
73
|
+
let loadingProvider = $state<AuthProvider | null>(null);
|
|
74
|
+
let error = $state<string | null>(null);
|
|
75
|
+
|
|
70
76
|
/**
|
|
71
77
|
* Get the callback URL for OAuth redirects.
|
|
72
78
|
* This URL is where Better Auth will redirect after OAuth completes.
|
|
@@ -76,6 +82,47 @@
|
|
|
76
82
|
return `${origin}/auth/callback?returnTo=${encodeURIComponent(returnTo)}`;
|
|
77
83
|
}
|
|
78
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Initiate social sign-in via Better Auth.
|
|
87
|
+
* Makes a JSON POST request and redirects to the OAuth provider.
|
|
88
|
+
*/
|
|
89
|
+
async function signInWithProvider(provider: AuthProvider) {
|
|
90
|
+
if (!browser || loadingProvider) return;
|
|
91
|
+
|
|
92
|
+
loadingProvider = provider;
|
|
93
|
+
error = null;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetch(GROVEAUTH_URLS.socialSignIn, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: {
|
|
99
|
+
"Content-Type": "application/json",
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify({
|
|
102
|
+
provider,
|
|
103
|
+
callbackURL: getCallbackUrl(),
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const data = (await response.json().catch(() => ({}))) as { message?: string };
|
|
109
|
+
throw new Error(data.message || `Sign-in failed (${response.status})`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const data = (await response.json()) as { url?: string };
|
|
113
|
+
|
|
114
|
+
// Better Auth returns { url: "https://accounts.google.com/..." }
|
|
115
|
+
if (data.url) {
|
|
116
|
+
window.location.href = data.url;
|
|
117
|
+
} else {
|
|
118
|
+
throw new Error("No redirect URL returned from auth server");
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
error = err instanceof Error ? err.message : "Sign-in failed";
|
|
122
|
+
loadingProvider = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
79
126
|
// For compact variant, use first available provider
|
|
80
127
|
const primaryProvider = $derived(availableProviders[0]);
|
|
81
128
|
</script>
|
|
@@ -87,20 +134,23 @@
|
|
|
87
134
|
<!-- Passkey uses its own button with WebAuthn ceremony -->
|
|
88
135
|
<PasskeyButton {returnTo} size="md" class={className} />
|
|
89
136
|
{:else}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
137
|
+
<GlassButton
|
|
138
|
+
variant="default"
|
|
139
|
+
size="md"
|
|
140
|
+
type="button"
|
|
141
|
+
class={className}
|
|
142
|
+
disabled={loadingProvider !== null}
|
|
143
|
+
onclick={() => signInWithProvider(primaryProvider)}
|
|
144
|
+
>
|
|
145
|
+
<ProviderIcon provider={primaryProvider} size={18} />
|
|
146
|
+
<span>
|
|
147
|
+
{#if loadingProvider === primaryProvider}
|
|
148
|
+
Redirecting...
|
|
149
|
+
{:else}
|
|
150
|
+
Sign in with {getProviderName(primaryProvider)}
|
|
151
|
+
{/if}
|
|
152
|
+
</span>
|
|
153
|
+
</GlassButton>
|
|
104
154
|
{/if}
|
|
105
155
|
{/if}
|
|
106
156
|
{:else if variant === "fullpage"}
|
|
@@ -130,6 +180,13 @@
|
|
|
130
180
|
{/if}
|
|
131
181
|
</div>
|
|
132
182
|
|
|
183
|
+
<!-- Error message -->
|
|
184
|
+
{#if error}
|
|
185
|
+
<div class="mb-4 p-3 rounded-lg bg-destructive/10 text-destructive text-sm text-center">
|
|
186
|
+
{error}
|
|
187
|
+
</div>
|
|
188
|
+
{/if}
|
|
189
|
+
|
|
133
190
|
<!-- Provider buttons -->
|
|
134
191
|
{#if availableProviders.length > 0}
|
|
135
192
|
<div class="space-y-3">
|
|
@@ -138,20 +195,23 @@
|
|
|
138
195
|
<!-- Passkey uses its own button with WebAuthn ceremony -->
|
|
139
196
|
<PasskeyButton {returnTo} />
|
|
140
197
|
{:else}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
198
|
+
<GlassButton
|
|
199
|
+
variant="default"
|
|
200
|
+
size="lg"
|
|
201
|
+
type="button"
|
|
202
|
+
class="w-full justify-start gap-3"
|
|
203
|
+
disabled={loadingProvider !== null}
|
|
204
|
+
onclick={() => signInWithProvider(provider)}
|
|
205
|
+
>
|
|
206
|
+
<ProviderIcon {provider} size={20} />
|
|
207
|
+
<span>
|
|
208
|
+
{#if loadingProvider === provider}
|
|
209
|
+
Redirecting...
|
|
210
|
+
{:else}
|
|
211
|
+
Continue with {getProviderName(provider)}
|
|
212
|
+
{/if}
|
|
213
|
+
</span>
|
|
214
|
+
</GlassButton>
|
|
155
215
|
{/if}
|
|
156
216
|
{/each}
|
|
157
217
|
</div>
|
|
@@ -183,6 +243,13 @@
|
|
|
183
243
|
</div>
|
|
184
244
|
{/if}
|
|
185
245
|
|
|
246
|
+
<!-- Error message -->
|
|
247
|
+
{#if error}
|
|
248
|
+
<div class="mb-4 p-3 rounded-lg bg-destructive/10 text-destructive text-sm text-center">
|
|
249
|
+
{error}
|
|
250
|
+
</div>
|
|
251
|
+
{/if}
|
|
252
|
+
|
|
186
253
|
<!-- Provider buttons -->
|
|
187
254
|
{#if availableProviders.length > 0}
|
|
188
255
|
<div class="space-y-3">
|
|
@@ -191,20 +258,23 @@
|
|
|
191
258
|
<!-- Passkey uses its own button with WebAuthn ceremony -->
|
|
192
259
|
<PasskeyButton {returnTo} />
|
|
193
260
|
{:else}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
261
|
+
<GlassButton
|
|
262
|
+
variant="default"
|
|
263
|
+
size="lg"
|
|
264
|
+
type="button"
|
|
265
|
+
class="w-full justify-start gap-3"
|
|
266
|
+
disabled={loadingProvider !== null}
|
|
267
|
+
onclick={() => signInWithProvider(provider)}
|
|
268
|
+
>
|
|
269
|
+
<ProviderIcon {provider} size={20} />
|
|
270
|
+
<span>
|
|
271
|
+
{#if loadingProvider === provider}
|
|
272
|
+
Redirecting...
|
|
273
|
+
{:else}
|
|
274
|
+
Continue with {getProviderName(provider)}
|
|
275
|
+
{/if}
|
|
276
|
+
</span>
|
|
277
|
+
</GlassButton>
|
|
208
278
|
{/if}
|
|
209
279
|
{/each}
|
|
210
280
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autumnsgrove/groveengine",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.96",
|
|
4
4
|
"description": "Multi-tenant blog engine for Grove Platform. Features gutter annotations, markdown editing, magic code auth, and Cloudflare Workers deployment.",
|
|
5
5
|
"author": "AutumnsGrove",
|
|
6
6
|
"license": "AGPL-3.0-only",
|