wiki-viewer 1.4.1 → 1.4.3

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 (159) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -27
  4. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  5. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  23. package/.next/standalone/.next/server/app/api/agent/activity/route.js +5 -5
  24. package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js +6 -6
  26. package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js +6 -6
  28. package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js +6 -6
  30. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js +4 -4
  32. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js +4 -4
  34. package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js +5 -5
  36. package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js +4 -4
  38. package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/agent/internal/span/route.js +8 -8
  40. package/.next/standalone/.next/server/app/api/agent/internal/span/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/agent/settings/route.js +7 -7
  42. package/.next/standalone/.next/server/app/api/agent/settings/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js +7 -7
  44. package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/auth/[...all]/route.js +3 -3
  47. package/.next/standalone/.next/server/app/api/auth/[...all]/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/system/auth-config/route.js +5 -5
  49. package/.next/standalone/.next/server/app/api/system/auth-config/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/api/system/auth-settings/route.js +4 -4
  51. package/.next/standalone/.next/server/app/api/system/auth-settings/route.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/api/system/browse/route.js +6 -6
  53. package/.next/standalone/.next/server/app/api/system/browse/route.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/api/system/clear-root/route.js +5 -5
  55. package/.next/standalone/.next/server/app/api/system/clear-root/route.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/api/system/config/route.js +5 -5
  57. package/.next/standalone/.next/server/app/api/system/config/route.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/api/system/pins/route.js +4 -4
  59. package/.next/standalone/.next/server/app/api/system/pins/route.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/api/system/reveal/route.js +4 -4
  61. package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/api/system/root-status/route.js +4 -4
  63. package/.next/standalone/.next/server/app/api/system/root-status/route.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/api/system/set-root/route.js +5 -5
  65. package/.next/standalone/.next/server/app/api/system/set-root/route.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/api/wiki/app/route.js +6 -6
  67. package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/api/wiki/content/route.js +7 -7
  69. package/.next/standalone/.next/server/app/api/wiki/content/route.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/api/wiki/folder/route.js +5 -5
  71. package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
  72. package/.next/standalone/.next/server/app/api/wiki/move/route.js +5 -5
  73. package/.next/standalone/.next/server/app/api/wiki/move/route.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/api/wiki/new-file/route.js +5 -5
  75. package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/api/wiki/page/route.js +5 -5
  77. package/.next/standalone/.next/server/app/api/wiki/page/route.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/api/wiki/route.js +5 -5
  79. package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
  80. package/.next/standalone/.next/server/app/api/wiki/slugs/route.js +5 -5
  81. package/.next/standalone/.next/server/app/api/wiki/slugs/route.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/api/wiki/upload/route.js +5 -5
  83. package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/api/wiki/watch/route.js +6 -6
  85. package/.next/standalone/.next/server/app/api/wiki/watch/route.js.nft.json +1 -1
  86. package/.next/standalone/.next/server/app/index.html +1 -1
  87. package/.next/standalone/.next/server/app/index.rsc +2 -2
  88. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  89. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  90. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  91. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  92. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  93. package/.next/standalone/.next/server/app/page.js +4 -4
  94. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  95. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  96. package/.next/standalone/.next/server/app/signin/page.js +9 -4
  97. package/.next/standalone/.next/server/app/signin/page.js.nft.json +1 -1
  98. package/.next/standalone/.next/server/app/signin/page_client-reference-manifest.js +1 -1
  99. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__03h3~3e._.js → [root-of-the-server]__04udrya._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__04g0j8i._.js → [root-of-the-server]__0kdn1le._.js} +2 -2
  101. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0d89o98._.js → src_lib_auth_server_ts_00f4e0h._.js} +3 -3
  102. package/.next/standalone/.next/server/chunks/ssr/0biy_@better-auth_core_dist_121m_lh._.js +5 -0
  103. package/.next/standalone/.next/server/chunks/ssr/0dhv_better-auth_dist_adapters_kysely-adapter_index_mjs_08ymlcb._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/ssr/0dti_kysely_dist_esm_0-t3o9q._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/ssr/0dti_kysely_dist_esm_01._58q._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/ssr/0p91_next_0f40gy0._.js +6 -0
  107. package/.next/standalone/.next/server/chunks/ssr/{0p91_next_dist_0~2d1tl._.js → 0p91_next_dist_10ud_sa._.js} +2 -2
  108. package/.next/standalone/.next/server/chunks/ssr/0sag_@better-auth_memory-adapter_dist_index_mjs_07-_ka6._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_bun-sqlite-dialect-DzNwOpKv_mjs_0lruvb8._.js +3 -0
  110. package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_d1-sqlite-dialect-C2B7YsIT_mjs_03n6~tc._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_index_mjs_0gi5w.w._.js +4 -0
  112. package/.next/standalone/.next/server/chunks/ssr/0ugq_@better-auth_kysely-adapter_dist_node-sqlite-dialect_mjs_0l4.y~0._.js +3 -0
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__026s8~z._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06mzg2t._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0btcd4o._.js +12 -0
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0c~og-9._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__134_q7l._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/ssr/_0pqaawz._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_051tcva._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0o~d81.._.js +1 -1
  121. package/.next/standalone/.next/server/chunks/ssr/src_app_signin_00yk0vm._.js +452 -0
  122. package/.next/standalone/.next/server/chunks/ssr/src_app_signin_signin-form_tsx_0eznlbd._.js +3 -0
  123. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  124. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  125. package/.next/standalone/.next/server/pages/404.html +1 -1
  126. package/.next/standalone/.next/server/pages/500.html +1 -1
  127. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  128. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  129. package/.next/standalone/.next/static/chunks/09-jtayuhrq~b.js +1 -0
  130. package/.next/standalone/.next/static/chunks/0uv7ozzpiyp2_.js +1 -0
  131. package/.next/standalone/.next/static/chunks/135c~rq_ux73w.js +1 -0
  132. package/.next/standalone/docs/agent-fs-plan.md +230 -0
  133. package/.next/standalone/package.json +1 -1
  134. package/.next/standalone/src/app/signin/page.tsx +10 -225
  135. package/.next/standalone/src/app/signin/signin-form.tsx +238 -0
  136. package/.next/standalone/src/lib/auth/server.ts +14 -7
  137. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  138. package/package.json +1 -1
  139. package/.next/standalone/.next/server/app/signin.html +0 -1
  140. package/.next/standalone/.next/server/app/signin.meta +0 -15
  141. package/.next/standalone/.next/server/app/signin.rsc +0 -23
  142. package/.next/standalone/.next/server/app/signin.segments/_full.segment.rsc +0 -23
  143. package/.next/standalone/.next/server/app/signin.segments/_head.segment.rsc +0 -6
  144. package/.next/standalone/.next/server/app/signin.segments/_index.segment.rsc +0 -7
  145. package/.next/standalone/.next/server/app/signin.segments/_tree.segment.rsc +0 -3
  146. package/.next/standalone/.next/server/app/signin.segments/signin/__PAGE__.segment.rsc +0 -9
  147. package/.next/standalone/.next/server/app/signin.segments/signin.segment.rsc +0 -5
  148. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__088hqzy._.js +0 -3
  149. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0c147fo._.js +0 -3
  150. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0z.1v1z._.js +0 -3
  151. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12zp2dm._.js +0 -3
  152. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0exqvyf._.js +0 -3
  153. package/.next/standalone/.next/server/chunks/ssr/src_app_signin_page_tsx_1143y49._.js +0 -3
  154. package/.next/standalone/.next/static/chunks/0-_39e4awbk8y.js +0 -1
  155. package/.next/standalone/.next/static/chunks/02bxx~2cg~s6~.js +0 -1
  156. package/.next/standalone/.next/static/chunks/0mj_f8ncf2tks.js +0 -1
  157. /package/.next/standalone/.next/static/{bNZLQMXPxlDzQicfJAh2f → kzOuSAQArtvD7uiAJ6ZLh}/_buildManifest.js +0 -0
  158. /package/.next/standalone/.next/static/{bNZLQMXPxlDzQicfJAh2f → kzOuSAQArtvD7uiAJ6ZLh}/_clientMiddlewareManifest.js +0 -0
  159. /package/.next/standalone/.next/static/{bNZLQMXPxlDzQicfJAh2f → kzOuSAQArtvD7uiAJ6ZLh}/_ssgManifest.js +0 -0
@@ -0,0 +1,238 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, type FormEvent } from "react";
4
+ import { authClient } from "@/lib/auth/client";
5
+
6
+ export default function SignInForm({
7
+ initialPasswordAuth,
8
+ initialGoogle,
9
+ }: {
10
+ initialPasswordAuth: boolean;
11
+ initialGoogle: boolean;
12
+ }) {
13
+ const [mode, setMode] = useState<"signin" | "signup">("signin");
14
+ const [email, setEmail] = useState("");
15
+ const [password, setPassword] = useState("");
16
+ const [name, setName] = useState("");
17
+ const [error, setError] = useState<string | null>(null);
18
+ const [loading, setLoading] = useState(false);
19
+ // Seed from server-rendered values so the first paint already matches the
20
+ // configured auth methods. This avoids a flash of the password form when it
21
+ // is disabled. The fetch below only refreshes in case config changed.
22
+ const [hasGoogle, setHasGoogle] = useState(initialGoogle);
23
+ const [passwordAuth, setPasswordAuth] = useState(initialPasswordAuth);
24
+ const [callbackURL, setCallbackURL] = useState("/");
25
+
26
+ useEffect(() => {
27
+ // Refresh which auth methods to show. Public endpoint, no session needed.
28
+ fetch("/api/system/auth-config", { credentials: "include" })
29
+ .then((r) => r.json())
30
+ .then((data: unknown) => {
31
+ if (data && typeof data === "object") {
32
+ const cfg = data as { google?: boolean; passwordAuth?: boolean };
33
+ setHasGoogle(Boolean(cfg.google));
34
+ setPasswordAuth(cfg.passwordAuth !== false);
35
+ }
36
+ })
37
+ .catch(() => {});
38
+
39
+ // Honour ?next= redirect param
40
+ const params = new URLSearchParams(window.location.search);
41
+ const next = params.get("next");
42
+ if (next && next.startsWith("/")) setCallbackURL(next);
43
+ }, []);
44
+
45
+ async function handleSubmit(e: FormEvent) {
46
+ e.preventDefault();
47
+ setError(null);
48
+ setLoading(true);
49
+ try {
50
+ if (mode === "signup") {
51
+ const res = await authClient.signUp.email({
52
+ email,
53
+ password,
54
+ name,
55
+ callbackURL,
56
+ });
57
+ if (res.error) {
58
+ setError(res.error.message ?? "Sign-up failed");
59
+ } else {
60
+ window.location.href = callbackURL;
61
+ }
62
+ } else {
63
+ const res = await authClient.signIn.email({
64
+ email,
65
+ password,
66
+ callbackURL,
67
+ });
68
+ if (res.error) {
69
+ const status = res.error.status;
70
+ if (status === 429) {
71
+ setError("Too many sign-in attempts. Wait a minute and try again.");
72
+ } else {
73
+ setError(res.error.message ?? "Sign-in failed");
74
+ }
75
+ } else {
76
+ window.location.href = callbackURL;
77
+ }
78
+ }
79
+ } catch (err: unknown) {
80
+ setError(err instanceof Error ? err.message : "Unexpected error");
81
+ } finally {
82
+ setLoading(false);
83
+ }
84
+ }
85
+
86
+ async function handleGoogle() {
87
+ setError(null);
88
+ setLoading(true);
89
+ try {
90
+ await authClient.signIn.social({ provider: "google", callbackURL });
91
+ } catch (err: unknown) {
92
+ setError(err instanceof Error ? err.message : "Google sign-in failed");
93
+ setLoading(false);
94
+ }
95
+ }
96
+
97
+ return (
98
+ <div className="min-h-screen flex items-center justify-center bg-background">
99
+ <div className="w-full max-w-sm rounded-lg border border-border bg-card p-8 shadow-sm">
100
+ <h1 className="mb-6 text-xl font-semibold text-foreground">Sign in to wiki-viewer</h1>
101
+
102
+ {hasGoogle && (
103
+ <>
104
+ <button
105
+ type="button"
106
+ onClick={handleGoogle}
107
+ disabled={loading}
108
+ className="flex w-full items-center justify-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium hover:bg-accent disabled:opacity-50"
109
+ >
110
+ <svg viewBox="0 0 24 24" className="h-4 w-4" aria-hidden="true">
111
+ <path
112
+ fill="#4285F4"
113
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
114
+ />
115
+ <path
116
+ fill="#34A853"
117
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
118
+ />
119
+ <path
120
+ fill="#FBBC05"
121
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z"
122
+ />
123
+ <path
124
+ fill="#EA4335"
125
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
126
+ />
127
+ </svg>
128
+ Continue with Google
129
+ </button>
130
+ {passwordAuth && (
131
+ <div className="my-4 flex items-center gap-3">
132
+ <hr className="flex-1 border-border" />
133
+ <span className="text-xs text-muted-foreground">or</span>
134
+ <hr className="flex-1 border-border" />
135
+ </div>
136
+ )}
137
+ </>
138
+ )}
139
+
140
+ {!passwordAuth && !hasGoogle && (
141
+ <p className="rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive">
142
+ No sign-in method is configured. Set GOOGLE_CLIENT_ID and
143
+ GOOGLE_CLIENT_SECRET, or enable email/password auth.
144
+ </p>
145
+ )}
146
+
147
+ {passwordAuth && (
148
+ <form onSubmit={handleSubmit} className="space-y-4">
149
+ {mode === "signup" && (
150
+ <div>
151
+ <label className="mb-1 block text-sm font-medium text-foreground" htmlFor="name">
152
+ Name
153
+ </label>
154
+ <input
155
+ id="name"
156
+ type="text"
157
+ autoComplete="name"
158
+ value={name}
159
+ onChange={(e) => setName(e.target.value)}
160
+ required
161
+ className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
162
+ />
163
+ </div>
164
+ )}
165
+ <div>
166
+ <label className="mb-1 block text-sm font-medium text-foreground" htmlFor="email">
167
+ Email
168
+ </label>
169
+ <input
170
+ id="email"
171
+ type="email"
172
+ autoComplete="email"
173
+ value={email}
174
+ onChange={(e) => setEmail(e.target.value)}
175
+ required
176
+ className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
177
+ />
178
+ </div>
179
+ <div>
180
+ <label
181
+ className="mb-1 block text-sm font-medium text-foreground"
182
+ htmlFor="password"
183
+ >
184
+ Password
185
+ </label>
186
+ <input
187
+ id="password"
188
+ type="password"
189
+ autoComplete={mode === "signup" ? "new-password" : "current-password"}
190
+ value={password}
191
+ onChange={(e) => setPassword(e.target.value)}
192
+ required
193
+ minLength={8}
194
+ className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
195
+ />
196
+ </div>
197
+
198
+ {error && (
199
+ <p className="rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive">
200
+ {error}
201
+ </p>
202
+ )}
203
+
204
+ <button
205
+ type="submit"
206
+ disabled={loading}
207
+ className="w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
208
+ >
209
+ {loading
210
+ ? mode === "signup"
211
+ ? "Creating account..."
212
+ : "Signing in..."
213
+ : mode === "signup"
214
+ ? "Create account"
215
+ : "Sign in"}
216
+ </button>
217
+ </form>
218
+ )}
219
+
220
+ {passwordAuth && (
221
+ <p className="mt-4 text-center text-sm text-muted-foreground">
222
+ {mode === "signin" ? "No account?" : "Already have an account?"}{" "}
223
+ <button
224
+ type="button"
225
+ onClick={() => {
226
+ setMode(mode === "signin" ? "signup" : "signin");
227
+ setError(null);
228
+ }}
229
+ className="font-medium text-foreground underline-offset-4 hover:underline"
230
+ >
231
+ {mode === "signin" ? "Sign up" : "Sign in"}
232
+ </button>
233
+ </p>
234
+ )}
235
+ </div>
236
+ </div>
237
+ );
238
+ }
@@ -93,11 +93,16 @@ if (passwordDisabled && !hasSocialProvider) {
93
93
  }
94
94
 
95
95
  // Forced account linking. By default Better Auth only auto-links a social
96
- // login to an existing account when both sides have a verified email. A
97
- // pre-existing password account with an unverified email blocks Google sign-in
98
- // with account_not_linked. Listing a provider here links it even when the
99
- // existing account is unverified. This trades a little account-takeover risk
100
- // for the ability to consolidate onto Google sign-in, so it is opt-in.
96
+ // login to an existing account when BOTH sides are email-verified. Two
97
+ // separate checks gate this (see better-auth oauth2/link-account):
98
+ // 1. the provider must report the email verified, OR the provider is trusted
99
+ // 2. the existing local account must be email-verified, unless
100
+ // requireLocalEmailVerified is false
101
+ // A pre-existing password account with emailVerified=0 fails check 2 even when
102
+ // the provider is trusted. So listing a provider in AUTH_TRUSTED_PROVIDERS also
103
+ // turns off requireLocalEmailVerified, otherwise Google sign-in still returns
104
+ // account_not_linked. This trades a little account-takeover risk for the
105
+ // ability to consolidate onto Google sign-in, so it is opt-in.
101
106
  const trustedProviders = (process.env.AUTH_TRUSTED_PROVIDERS ?? "")
102
107
  .split(",")
103
108
  .map((p) => p.trim())
@@ -110,8 +115,10 @@ export const auth = betterAuth({
110
115
  account: {
111
116
  accountLinking: {
112
117
  enabled: true,
113
- ...(trustedProviders.length ? { trustedProviders } : {}),
114
- },
118
+ ...(trustedProviders.length
119
+ ? { trustedProviders, requireLocalEmailVerified: false }
120
+ : {}),
121
+ } as Record<string, unknown>,
115
122
  },
116
123
  emailAndPassword: {
117
124
  enabled: passwordAuthEnabled,