@fayz-ai/auth 0.44.0 → 0.48.0
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/README.md +4 -0
- package/dist/chunk-2MO4QTQA.js +78 -0
- package/dist/chunk-2MO4QTQA.js.map +1 -0
- package/dist/chunk-2S7KVJQO.js +18 -0
- package/dist/chunk-2S7KVJQO.js.map +1 -0
- package/dist/chunk-4HVM2UJS.js +30 -0
- package/dist/chunk-4HVM2UJS.js.map +1 -0
- package/dist/chunk-55RFOMW5.js +15 -0
- package/dist/chunk-55RFOMW5.js.map +1 -0
- package/dist/chunk-7JYNJQJJ.js +138 -0
- package/dist/chunk-7JYNJQJJ.js.map +1 -0
- package/dist/chunk-BGCCXJJA.js +174 -0
- package/dist/chunk-BGCCXJJA.js.map +1 -0
- package/dist/chunk-CV4LQYNX.js +109 -0
- package/dist/chunk-CV4LQYNX.js.map +1 -0
- package/dist/chunk-ERIPMYL2.js +137 -0
- package/dist/chunk-ERIPMYL2.js.map +1 -0
- package/dist/chunk-HKILBDG2.js +158 -0
- package/dist/chunk-HKILBDG2.js.map +1 -0
- package/dist/chunk-KLQDHFVS.js +11 -0
- package/dist/chunk-KLQDHFVS.js.map +1 -0
- package/dist/chunk-OI74ER2Z.js +31 -0
- package/dist/chunk-OI74ER2Z.js.map +1 -0
- package/dist/chunk-Q6DFI35B.js +187 -0
- package/dist/chunk-Q6DFI35B.js.map +1 -0
- package/dist/chunk-QJRTW7Q3.js +90 -0
- package/dist/chunk-QJRTW7Q3.js.map +1 -0
- package/dist/chunk-REU4D5UF.js +44 -0
- package/dist/chunk-REU4D5UF.js.map +1 -0
- package/dist/chunk-SMFXGZN7.js +18 -0
- package/dist/chunk-SMFXGZN7.js.map +1 -0
- package/dist/chunk-W35GQEGU.js +80 -0
- package/dist/chunk-W35GQEGU.js.map +1 -0
- package/dist/chunk-W7565FXD.js +64 -0
- package/dist/chunk-W7565FXD.js.map +1 -0
- package/dist/chunk-WZVZMNLB.js +48 -0
- package/dist/chunk-WZVZMNLB.js.map +1 -0
- package/dist/components/AmbassadorBubbles.d.ts +9 -0
- package/dist/components/AmbassadorBubbles.d.ts.map +1 -0
- package/dist/components/AmbassadorBubbles.js +3 -0
- package/dist/components/AmbassadorBubbles.js.map +1 -0
- package/dist/components/TrialPromise.d.ts +12 -0
- package/dist/components/TrialPromise.d.ts.map +1 -0
- package/dist/components/TrialPromise.js +3 -0
- package/dist/components/TrialPromise.js.map +1 -0
- package/dist/components/form-classes.d.ts +3 -0
- package/dist/components/form-classes.d.ts.map +1 -0
- package/dist/components/form-classes.js +3 -0
- package/dist/components/form-classes.js.map +1 -0
- package/dist/context.js +5 -0
- package/dist/context.js.map +1 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/mock.js +3 -0
- package/dist/data/mock.js.map +1 -0
- package/dist/data/supabase.d.ts.map +1 -0
- package/dist/data/supabase.js +4 -0
- package/dist/data/supabase.js.map +1 -0
- package/dist/domain/auth-config.d.ts +71 -0
- package/dist/domain/auth-config.d.ts.map +1 -0
- package/dist/domain/auth-config.js +3 -0
- package/dist/domain/auth-config.js.map +1 -0
- package/dist/domain/auth-link.d.ts +26 -0
- package/dist/domain/auth-link.d.ts.map +1 -0
- package/dist/domain/auth-link.js +3 -0
- package/dist/domain/auth-link.js.map +1 -0
- package/dist/domain/password.d.ts +5 -0
- package/dist/domain/password.d.ts.map +1 -0
- package/dist/domain/password.js +3 -0
- package/dist/domain/password.js.map +1 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -573
- package/dist/index.js.map +1 -1
- package/dist/lib/runtime.d.ts +6 -0
- package/dist/lib/runtime.d.ts.map +1 -0
- package/dist/lib/runtime.js +51 -0
- package/dist/lib/runtime.js.map +1 -0
- package/dist/lib/url-intent.d.ts +1 -25
- package/dist/lib/url-intent.d.ts.map +1 -1
- package/dist/lib/url-intent.js +3 -0
- package/dist/lib/url-intent.js.map +1 -0
- package/dist/store.js +3 -0
- package/dist/store.js.map +1 -0
- package/dist/views/AuthCallbackPage.d.ts +7 -0
- package/dist/views/AuthCallbackPage.d.ts.map +1 -0
- package/dist/views/AuthCallbackPage.js +33 -0
- package/dist/views/AuthCallbackPage.js.map +1 -0
- package/dist/views/AuthGate.d.ts +9 -0
- package/dist/views/AuthGate.d.ts.map +1 -0
- package/dist/views/AuthGate.js +28 -0
- package/dist/views/AuthGate.js.map +1 -0
- package/dist/views/ForgotPasswordForm.d.ts +18 -0
- package/dist/views/ForgotPasswordForm.d.ts.map +1 -0
- package/dist/views/ForgotPasswordForm.js +7 -0
- package/dist/views/ForgotPasswordForm.js.map +1 -0
- package/dist/views/LoginForm.d.ts +16 -0
- package/dist/views/LoginForm.d.ts.map +1 -0
- package/dist/views/LoginForm.js +8 -0
- package/dist/views/LoginForm.js.map +1 -0
- package/dist/views/LoginPage.d.ts +34 -0
- package/dist/views/LoginPage.d.ts.map +1 -0
- package/dist/views/LoginPage.js +16 -0
- package/dist/views/LoginPage.js.map +1 -0
- package/dist/views/OAuthButtons.d.ts +18 -0
- package/dist/views/OAuthButtons.d.ts.map +1 -0
- package/dist/views/OAuthButtons.js +7 -0
- package/dist/views/OAuthButtons.js.map +1 -0
- package/dist/views/ResetPasswordForm.d.ts +6 -0
- package/dist/views/ResetPasswordForm.d.ts.map +1 -0
- package/dist/views/ResetPasswordForm.js +53 -0
- package/dist/views/ResetPasswordForm.js.map +1 -0
- package/dist/views/ResetPasswordPage.d.ts +7 -0
- package/dist/views/ResetPasswordPage.d.ts.map +1 -0
- package/dist/views/ResetPasswordPage.js +16 -0
- package/dist/views/ResetPasswordPage.js.map +1 -0
- package/dist/views/SetPasswordPage.d.ts +15 -0
- package/dist/views/SetPasswordPage.d.ts.map +1 -0
- package/dist/views/SetPasswordPage.js +9 -0
- package/dist/views/SetPasswordPage.js.map +1 -0
- package/dist/views/SignupForm.d.ts +24 -0
- package/dist/views/SignupForm.d.ts.map +1 -0
- package/dist/views/SignupForm.js +9 -0
- package/dist/views/SignupForm.js.map +1 -0
- package/dist/views/VerifyEmailNotice.d.ts +15 -0
- package/dist/views/VerifyEmailNotice.d.ts.map +1 -0
- package/dist/views/VerifyEmailNotice.js +6 -0
- package/dist/views/VerifyEmailNotice.js.map +1 -0
- package/dist/views/usePasswordSetupGate.d.ts +20 -0
- package/dist/views/usePasswordSetupGate.d.ts.map +1 -0
- package/dist/views/usePasswordSetupGate.js +27 -0
- package/dist/views/usePasswordSetupGate.js.map +1 -0
- package/package.json +14 -3
- package/dist/adapters/mock.d.ts.map +0 -1
- package/dist/adapters/supabase.d.ts.map +0 -1
- /package/dist/{adapters → data}/mock.d.ts +0 -0
- /package/dist/{adapters → data}/supabase.d.ts +0 -0
package/README.md
CHANGED
|
@@ -16,6 +16,10 @@ This is the auth layer for composed Fayz SaaS apps: drop in `AuthProvider`, pick
|
|
|
16
16
|
- `createMockAuthAdapter` — deterministic auth for tests, demos, and previews (`MockUser`)
|
|
17
17
|
- `AuthProvider` + `useAuth` — React context and hook for the current user and session
|
|
18
18
|
- `useAuthStore` — the underlying Zustand store (`AuthState`, `AuthStore`) for advanced access
|
|
19
|
+
- Screens and rules, imported by path: `@fayz-ai/auth/views/AuthGate`, `views/LoginPage`,
|
|
20
|
+
`views/SetPasswordPage`, `views/usePasswordSetupGate`, `lib/runtime` (`createAuthRuntime`),
|
|
21
|
+
`domain/auth-config` (`AuthPluginOptions`, `LoginAmbassador`), `domain/password`. Terms in
|
|
22
|
+
[GLOSSARY.md](GLOSSARY.md).
|
|
19
23
|
|
|
20
24
|
## Install
|
|
21
25
|
```bash
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/lib/url-intent.ts
|
|
2
|
+
var AUTH_INTENT_PARAM = "fayz_auth";
|
|
3
|
+
var RESET_PATH = "/auth/reset-password";
|
|
4
|
+
function snapshot() {
|
|
5
|
+
const loc = typeof window === "undefined" ? void 0 : window.location;
|
|
6
|
+
if (!loc) return null;
|
|
7
|
+
return {
|
|
8
|
+
pathname: loc.pathname ?? "",
|
|
9
|
+
search: loc.search ?? "",
|
|
10
|
+
hash: loc.hash ?? ""
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
var initialUrl = snapshot();
|
|
14
|
+
function hashParams(hash) {
|
|
15
|
+
const raw = hash.replace(/^#/, "");
|
|
16
|
+
if (!raw || !raw.includes("=")) return new URLSearchParams();
|
|
17
|
+
return new URLSearchParams(raw);
|
|
18
|
+
}
|
|
19
|
+
function readAuthUrlIntent(url) {
|
|
20
|
+
const loc = url ?? initialUrl;
|
|
21
|
+
if (!loc) return null;
|
|
22
|
+
const query = new URLSearchParams(loc.search);
|
|
23
|
+
const hash = hashParams(loc.hash);
|
|
24
|
+
const param = (key) => hash.get(key) ?? query.get(key);
|
|
25
|
+
const errorCode = param("error_code") ?? param("error");
|
|
26
|
+
const errorDescription = param("error_description")?.replace(/\+/g, " ");
|
|
27
|
+
if (errorCode && /otp|expired|link/i.test(`${errorCode} ${errorDescription ?? ""}`)) {
|
|
28
|
+
return { kind: "expired", message: errorDescription ?? void 0 };
|
|
29
|
+
}
|
|
30
|
+
const marker = query.get(AUTH_INTENT_PARAM);
|
|
31
|
+
const type = param("type");
|
|
32
|
+
if (type === "recovery" || marker === "recovery") return { kind: "recovery" };
|
|
33
|
+
const path = loc.pathname.replace(/\/+$/, "");
|
|
34
|
+
if (path.endsWith(RESET_PATH)) return { kind: "recovery" };
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
var APP_HANDOFF_PARAM = "handoff";
|
|
38
|
+
var APP_HANDOFF_REFRESH_PARAM = "handoff_r";
|
|
39
|
+
var APP_HANDOFF_TENANT_PARAM = "account";
|
|
40
|
+
function readAppHandoff(url) {
|
|
41
|
+
const loc = url ?? initialUrl;
|
|
42
|
+
if (!loc) return null;
|
|
43
|
+
const hash = hashParams(loc.hash);
|
|
44
|
+
const accessToken = hash.get(APP_HANDOFF_PARAM);
|
|
45
|
+
const refreshToken = hash.get(APP_HANDOFF_REFRESH_PARAM);
|
|
46
|
+
if (!accessToken || !refreshToken) return null;
|
|
47
|
+
return {
|
|
48
|
+
accessToken,
|
|
49
|
+
refreshToken,
|
|
50
|
+
tenantId: hash.get(APP_HANDOFF_TENANT_PARAM) || null
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function buildAppHandoffUrl(appUrl, session, tenantId) {
|
|
54
|
+
if (!session.accessToken || !session.refreshToken) return appUrl;
|
|
55
|
+
const params = new URLSearchParams();
|
|
56
|
+
params.set(APP_HANDOFF_PARAM, session.accessToken);
|
|
57
|
+
params.set(APP_HANDOFF_REFRESH_PARAM, session.refreshToken);
|
|
58
|
+
if (tenantId) params.set(APP_HANDOFF_TENANT_PARAM, tenantId);
|
|
59
|
+
const base = appUrl.split("#")[0];
|
|
60
|
+
return `${base}#${params.toString()}`;
|
|
61
|
+
}
|
|
62
|
+
function clearAuthUrlTokens() {
|
|
63
|
+
if (typeof window === "undefined") return;
|
|
64
|
+
const url = new URL(window.location.href);
|
|
65
|
+
url.searchParams.delete(AUTH_INTENT_PARAM);
|
|
66
|
+
if (url.hash.includes("=")) url.hash = "";
|
|
67
|
+
window.history.replaceState(window.history.state, "", url.toString());
|
|
68
|
+
}
|
|
69
|
+
function withAuthIntent(url, intent) {
|
|
70
|
+
const base = typeof window === "undefined" ? "http://localhost" : window.location.origin;
|
|
71
|
+
const parsed = new URL(url, base);
|
|
72
|
+
parsed.searchParams.set(AUTH_INTENT_PARAM, intent);
|
|
73
|
+
return /^https?:\/\//i.test(url) ? parsed.toString() : `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { APP_HANDOFF_PARAM, APP_HANDOFF_REFRESH_PARAM, APP_HANDOFF_TENANT_PARAM, AUTH_INTENT_PARAM, buildAppHandoffUrl, clearAuthUrlTokens, readAppHandoff, readAuthUrlIntent, withAuthIntent };
|
|
77
|
+
//# sourceMappingURL=chunk-2MO4QTQA.js.map
|
|
78
|
+
//# sourceMappingURL=chunk-2MO4QTQA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/url-intent.ts"],"names":[],"mappings":"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","file":"chunk-2MO4QTQA.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Auth link intent — what the URL the user just arrived on is asking for.\n//\n// A Supabase recovery / magic link lands on the app with the tokens in the URL\n// FRAGMENT (implicit flow is the supabase-js default), e.g.\n//\n// https://app.example.com/#access_token=…&refresh_token=…&type=recovery\n//\n// GoTrue then consumes that fragment and wipes it (`window.location.hash = ''`)\n// as soon as the client initialises — so by the time React renders, the reason\n// the user is here is gone. Hence the module-load snapshot: this file is\n// imported long before any createClient() call, so it always sees the original\n// URL.\n//\n// Errors arrive the same way, without a `type`:\n//\n// https://app.example.com/#error=access_denied&error_code=otp_expired&…\n// ---------------------------------------------------------------------------\n\nimport type { AuthUrlIntent } from '../domain/auth-link'\n\n/** Query param apps can put on their own redirect URLs to force the\n * set-password screen (e.g. when they override `resetPasswordUrl`). */\nexport const AUTH_INTENT_PARAM = 'fayz_auth'\n\n/** Path segment the SDK historically used as the reset-password redirect.\n * Still honoured so apps that configured it keep working. */\nconst RESET_PATH = '/auth/reset-password'\n\ninterface UrlSnapshot {\n pathname: string\n search: string\n hash: string\n}\n\nfunction snapshot(): UrlSnapshot | null {\n // `window` without a `location` is a real environment, not a hypothetical:\n // the SSR shim the manifest derivation runs under defines the global and not\n // the URL. This module is imported at the top of the auth context, so it is\n // evaluated anywhere the context is — including there, where reading\n // `window.location.pathname` threw and took the whole derivation with it.\n const loc = typeof window === 'undefined' ? undefined : window.location\n if (!loc) return null\n return {\n pathname: loc.pathname ?? '',\n search: loc.search ?? '',\n hash: loc.hash ?? '',\n }\n}\n\n/** Taken at import time — before any Supabase client can strip the fragment. */\nconst initialUrl: UrlSnapshot | null = snapshot()\n\n/** The fragment can be either auth params (`access_token=…&type=recovery`) or a\n * hash-router path (`#/dashboard`). Only the former parses into pairs. */\nfunction hashParams(hash: string): URLSearchParams {\n const raw = hash.replace(/^#/, '')\n if (!raw || !raw.includes('=')) return new URLSearchParams()\n return new URLSearchParams(raw)\n}\n\nexport function readAuthUrlIntent(url?: UrlSnapshot | null): AuthUrlIntent | null {\n const loc = url ?? initialUrl\n if (!loc) return null\n\n const query = new URLSearchParams(loc.search)\n const hash = hashParams(loc.hash)\n const param = (key: string) => hash.get(key) ?? query.get(key)\n\n const errorCode = param('error_code') ?? param('error')\n const errorDescription = param('error_description')?.replace(/\\+/g, ' ')\n // A dead one-time link (`error_code=otp_expired`). Deliberately narrow: an\n // OAuth `access_denied` is a different failure and must not land here. GoTrue\n // does not echo `type` on error redirects, so recovery and invite links are\n // indistinguishable at this point — the screen offers a fresh link either way.\n if (errorCode && /otp|expired|link/i.test(`${errorCode} ${errorDescription ?? ''}`)) {\n return { kind: 'expired', message: errorDescription ?? undefined }\n }\n\n const marker = query.get(AUTH_INTENT_PARAM)\n const type = param('type')\n if (type === 'recovery' || marker === 'recovery') return { kind: 'recovery' }\n\n // `/auth/reset-password` with no tokens at all: the user opened the reset URL\n // directly, or the tokens were already consumed by a previous page load.\n const path = loc.pathname.replace(/\\/+$/, '')\n if (path.endsWith(RESET_PATH)) return { kind: 'recovery' }\n\n return null\n}\n\n// ---------------------------------------------------------------------------\n// App handoff — arriving from a sibling application already signed in.\n//\n// The products share one Supabase project now, so the same person is one\n// `auth.users` row across all of them. They do NOT share a session: each app is\n// its own origin, and supabase-js keeps the session in that origin's\n// localStorage. Same account, same database, and still a password prompt — which\n// is the friction the switcher exists to remove.\n//\n// So the switcher carries the session over in the URL FRAGMENT, which is the\n// same channel GoTrue's own magic links use and for the same reason: a fragment\n// is never sent to a server, never reaches an access log, and never lands in a\n// Referer header. The module-load snapshot above is what makes reading it\n// reliable — supabase-js wipes the hash as soon as it initialises.\n//\n// Two things keep this from being a hole, and both are required:\n//\n// • The DESTINATION only adopts a handoff whose tokens actually verify — it\n// calls setSession, so a forged fragment fails at GoTrue, not at us.\n// • The SOURCE only ever writes this fragment to an origin listed in\n// `app.apps.url`. A tile is not a free-form link.\n//\n// The honest alternative is one origin with paths per app (/app/resto,\n// /app/loja), where localStorage is shared and no token travels at all. That is\n// a deployment change, and this is the same result without one.\n// ---------------------------------------------------------------------------\n\n/**\n * Fragment keys carrying a sibling app's session.\n *\n * Two keys and not one packed value: a JWT is `header.payload.signature`, so\n * any separator that looked natural — a dot above all — also occurs inside the\n * access token. Packing them cost an afternoon and produced `Invalid JWT\n * structure` at the far end, which reads like a forged token rather than like a\n * string that was cut in the wrong place. Both keys are required, so a\n * half-copied fragment is still a miss.\n */\nexport const APP_HANDOFF_PARAM = 'handoff'\nexport const APP_HANDOFF_REFRESH_PARAM = 'handoff_r'\n/** Fragment key naming the account to land in, so the arrival is not a guess. */\nexport const APP_HANDOFF_TENANT_PARAM = 'account'\n\nexport interface AppHandoff {\n accessToken: string\n refreshToken: string\n /** Which account the user was looking at. Null means \"whichever you'd pick\". */\n tenantId: string | null\n}\n\n/**\n * The session a sibling app handed over, if this page load is such an arrival.\n *\n * Returns null for every ordinary load, which is the overwhelming majority —\n * so a caller can ask on every boot without paying for it.\n */\nexport function readAppHandoff(url?: UrlSnapshot | null): AppHandoff | null {\n const loc = url ?? initialUrl\n if (!loc) return null\n\n const hash = hashParams(loc.hash)\n const accessToken = hash.get(APP_HANDOFF_PARAM)\n const refreshToken = hash.get(APP_HANDOFF_REFRESH_PARAM)\n if (!accessToken || !refreshToken) return null\n\n return {\n accessToken,\n refreshToken,\n tenantId: hash.get(APP_HANDOFF_TENANT_PARAM) || null,\n }\n}\n\n/** Pack a session into the fragment the destination will read. */\nexport function buildAppHandoffUrl(\n appUrl: string,\n session: { accessToken: string; refreshToken: string },\n tenantId?: string | null,\n): string {\n if (!session.accessToken || !session.refreshToken) return appUrl\n const params = new URLSearchParams()\n params.set(APP_HANDOFF_PARAM, session.accessToken)\n params.set(APP_HANDOFF_REFRESH_PARAM, session.refreshToken)\n if (tenantId) params.set(APP_HANDOFF_TENANT_PARAM, tenantId)\n // The hash is replaced, not appended to: an app URL that already carries a\n // hash-router path would otherwise arrive with both, and neither would parse.\n const base = appUrl.split('#')[0]\n return `${base}#${params.toString()}`\n}\n\n/** Strip auth leftovers from the address bar once the flow is finished, so a\n * reload does not put the user back on the set-password screen (and so the\n * hash router gets a clean slate). */\nexport function clearAuthUrlTokens(): void {\n if (typeof window === 'undefined') return\n const url = new URL(window.location.href)\n url.searchParams.delete(AUTH_INTENT_PARAM)\n if (url.hash.includes('=')) url.hash = ''\n window.history.replaceState(window.history.state, '', url.toString())\n}\n\n/** Add the intent marker to a redirect URL. Absolute or relative; existing\n * params are preserved. */\nexport function withAuthIntent(url: string, intent: 'recovery'): string {\n const base = typeof window === 'undefined' ? 'http://localhost' : window.location.origin\n const parsed = new URL(url, base)\n parsed.searchParams.set(AUTH_INTENT_PARAM, intent)\n return /^https?:\\/\\//i.test(url) ? parsed.toString() : `${parsed.pathname}${parsed.search}${parsed.hash}`\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/domain/auth-link.ts
|
|
2
|
+
var NOTHING = { reason: null, expired: false, checking: false };
|
|
3
|
+
function decidePasswordSetup(input) {
|
|
4
|
+
const { intent, isLoading, isAuthenticated, mustSetPassword } = input;
|
|
5
|
+
if (intent?.kind === "expired") return { reason: "recovery", expired: true, checking: false };
|
|
6
|
+
if (intent?.kind === "recovery") {
|
|
7
|
+
if (isLoading) return { reason: "recovery", expired: false, checking: true };
|
|
8
|
+
return { reason: "recovery", expired: !isAuthenticated, checking: false };
|
|
9
|
+
}
|
|
10
|
+
if (!isLoading && isAuthenticated && mustSetPassword) {
|
|
11
|
+
return { reason: "invite", expired: false, checking: false };
|
|
12
|
+
}
|
|
13
|
+
return NOTHING;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { decidePasswordSetup };
|
|
17
|
+
//# sourceMappingURL=chunk-2S7KVJQO.js.map
|
|
18
|
+
//# sourceMappingURL=chunk-2S7KVJQO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/domain/auth-link.ts"],"names":[],"mappings":";AAuBA,IAAM,UAAiC,EAAE,MAAA,EAAQ,MAAM,OAAA,EAAS,KAAA,EAAO,UAAU,KAAA,EAAM;AAEhF,SAAS,oBAAoB,KAAA,EAAkD;AACpF,EAAA,MAAM,EAAE,MAAA,EAAQ,SAAA,EAAW,eAAA,EAAiB,iBAAgB,GAAI,KAAA;AAEhE,EAAA,IAAI,MAAA,EAAQ,IAAA,KAAS,SAAA,EAAW,OAAO,EAAE,QAAQ,UAAA,EAAY,OAAA,EAAS,IAAA,EAAM,QAAA,EAAU,KAAA,EAAM;AAE5F,EAAA,IAAI,MAAA,EAAQ,SAAS,UAAA,EAAY;AAC/B,IAAA,IAAI,SAAA,SAAkB,EAAE,MAAA,EAAQ,YAAY,OAAA,EAAS,KAAA,EAAO,UAAU,IAAA,EAAK;AAE3E,IAAA,OAAO,EAAE,MAAA,EAAQ,UAAA,EAAY,SAAS,CAAC,eAAA,EAAiB,UAAU,KAAA,EAAM;AAAA,EAC1E;AAEA,EAAA,IAAI,CAAC,SAAA,IAAa,eAAA,IAAmB,eAAA,EAAiB;AACpD,IAAA,OAAO,EAAE,MAAA,EAAQ,QAAA,EAAU,OAAA,EAAS,KAAA,EAAO,UAAU,KAAA,EAAM;AAAA,EAC7D;AAEA,EAAA,OAAO,OAAA;AACT","file":"chunk-2S7KVJQO.js","sourcesContent":["export type AuthUrlIntent =\n /** A password-recovery link — the user must choose a new password now. */\n | { kind: 'recovery' }\n /** A one-time link that was already used or has expired. */\n | { kind: 'expired'; message?: string }\n\nexport type PasswordSetupReason = 'recovery' | 'invite'\n\nexport interface PasswordSetupInput {\n intent: AuthUrlIntent | null\n /** Session still resolving — the link's tokens are exchanged asynchronously. */\n isLoading: boolean\n isAuthenticated: boolean\n /** AuthUser.mustSetPassword — an invitee who never chose one. */\n mustSetPassword?: boolean\n}\n\nexport interface PasswordSetupDecision {\n reason: PasswordSetupReason | null\n expired: boolean\n checking: boolean\n}\n\nconst NOTHING: PasswordSetupDecision = { reason: null, expired: false, checking: false }\n\nexport function decidePasswordSetup(input: PasswordSetupInput): PasswordSetupDecision {\n const { intent, isLoading, isAuthenticated, mustSetPassword } = input\n\n if (intent?.kind === 'expired') return { reason: 'recovery', expired: true, checking: false }\n\n if (intent?.kind === 'recovery') {\n if (isLoading) return { reason: 'recovery', expired: false, checking: true }\n // A recovery link with no session behind it was already used or has expired.\n return { reason: 'recovery', expired: !isAuthenticated, checking: false }\n }\n\n if (!isLoading && isAuthenticated && mustSetPassword) {\n return { reason: 'invite', expired: false, checking: false }\n }\n\n return NOTHING\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/domain/auth-config.ts
|
|
2
|
+
function chooseAuthProvider(options) {
|
|
3
|
+
const configured = options.adapter ?? options.provider;
|
|
4
|
+
if (configured) return configured;
|
|
5
|
+
return options.supabase?.url ? "supabase" : "mock";
|
|
6
|
+
}
|
|
7
|
+
function resolveAuthRoutes(routes = {}) {
|
|
8
|
+
return {
|
|
9
|
+
login: routes.login ?? "/login",
|
|
10
|
+
callback: routes.callback ?? "/auth/callback",
|
|
11
|
+
// Recovery links are recognised by their own params, so the app root needs no route.
|
|
12
|
+
resetPassword: routes.resetPassword ?? "/",
|
|
13
|
+
afterSignIn: routes.afterSignIn ?? "/"
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function resolveOAuth(oauth = {}) {
|
|
17
|
+
return {
|
|
18
|
+
enabled: oauth.enabled ?? false,
|
|
19
|
+
providers: oauth.providers ?? ["google"],
|
|
20
|
+
comingSoon: oauth.comingSoon ?? ["google"]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
var SIGNUP_HASH = /^#\/?signup\/?$/i;
|
|
24
|
+
function isSignupHash(hash) {
|
|
25
|
+
return SIGNUP_HASH.test(hash);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { chooseAuthProvider, isSignupHash, resolveAuthRoutes, resolveOAuth };
|
|
29
|
+
//# sourceMappingURL=chunk-4HVM2UJS.js.map
|
|
30
|
+
//# sourceMappingURL=chunk-4HVM2UJS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/domain/auth-config.ts"],"names":[],"mappings":";AAyEO,SAAS,mBAAmB,OAAA,EAA2D;AAC5F,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,OAAA,IAAW,OAAA,CAAQ,QAAA;AAC9C,EAAA,IAAI,YAAY,OAAO,UAAA;AACvB,EAAA,OAAO,OAAA,CAAQ,QAAA,EAAU,GAAA,GAAM,UAAA,GAAa,MAAA;AAC9C;AAEO,SAAS,iBAAA,CAAkB,MAAA,GAA2B,EAAC,EAA+B;AAC3F,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,OAAO,KAAA,IAAS,QAAA;AAAA,IACvB,QAAA,EAAU,OAAO,QAAA,IAAY,gBAAA;AAAA;AAAA,IAE7B,aAAA,EAAe,OAAO,aAAA,IAAiB,GAAA;AAAA,IACvC,WAAA,EAAa,OAAO,WAAA,IAAe;AAAA,GACrC;AACF;AAEO,SAAS,YAAA,CAAa,KAAA,GAAyB,EAAC,EAA8B;AACnF,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,MAAM,OAAA,IAAW,KAAA;AAAA,IAC1B,SAAA,EAAW,KAAA,CAAM,SAAA,IAAa,CAAC,QAAQ,CAAA;AAAA,IACvC,UAAA,EAAY,KAAA,CAAM,UAAA,IAAc,CAAC,QAAQ;AAAA,GAC3C;AACF;AAEA,IAAM,WAAA,GAAc,kBAAA;AAGb,SAAS,aAAa,IAAA,EAAuB;AAClD,EAAA,OAAO,WAAA,CAAY,KAAK,IAAI,CAAA;AAC9B","file":"chunk-4HVM2UJS.js","sourcesContent":["import type React from 'react'\nimport type { AuthAdapter, AuthProvider } from '@fayz-ai/core'\nimport type { SupabaseClient } from '@supabase/supabase-js'\n\nexport type AuthProviderKey = 'supabase' | 'mock' | (string & {})\nexport type AuthLayout = 'split' | 'centered'\nexport type AuthFormView = 'login' | 'signup' | 'recovery'\nexport type OAuthProviderKey = Exclude<AuthProvider, 'email'>\n\n/** A product ambassador rendered as a floating circular photo bubble on the\n * split-login decorative panel. `image` may be a local path or a data URI. */\nexport interface LoginAmbassador {\n name: string\n role?: string\n image: string\n}\n\nexport interface AuthRoutesConfig {\n login?: string\n callback?: string\n resetPassword?: string\n afterSignIn?: string\n}\n\nexport interface AuthOAuthConfig {\n enabled?: boolean\n providers?: OAuthProviderKey[]\n /**\n * Providers rendered on the login page but not able to sign anybody in yet:\n * disabled, marked \"em breve\", no consent screen. Defaults to `['google']`\n * while the grant is not wired — an app that has it working passes `[]`.\n */\n comingSoon?: OAuthProviderKey[]\n}\n\nexport interface AuthSupabaseConfig {\n url?: string\n anonKey?: string\n client?: SupabaseClient\n callbackUrl?: string\n resetPasswordUrl?: string\n}\n\nexport interface AuthPluginOptions {\n provider?: AuthProviderKey | AuthAdapter\n adapter?: AuthProviderKey | AuthAdapter\n supabase?: AuthSupabaseConfig\n requireAuth?: boolean\n layout?: AuthLayout\n logo?: React.ReactNode\n tagline?: string\n description?: string\n loginAmbassadors?: LoginAmbassador[]\n loginAmbassadorsLabel?: string\n oauth?: AuthOAuthConfig\n routes?: AuthRoutesConfig\n}\n\nexport interface ResolvedAuthPlugin {\n kind: 'auth'\n adapter: AuthAdapter\n requireAuth: boolean\n layout: AuthLayout\n logo?: React.ReactNode\n tagline?: string\n description?: string\n loginAmbassadors?: LoginAmbassador[]\n loginAmbassadorsLabel?: string\n oauth: Required<AuthOAuthConfig>\n routes: Required<AuthRoutesConfig>\n}\n\n/** The adapter the options ask for: one passed in, a named provider, or Supabase when a URL is set. */\nexport function chooseAuthProvider(options: AuthPluginOptions): AuthAdapter | AuthProviderKey {\n const configured = options.adapter ?? options.provider\n if (configured) return configured\n return options.supabase?.url ? 'supabase' : 'mock'\n}\n\nexport function resolveAuthRoutes(routes: AuthRoutesConfig = {}): Required<AuthRoutesConfig> {\n return {\n login: routes.login ?? '/login',\n callback: routes.callback ?? '/auth/callback',\n // Recovery links are recognised by their own params, so the app root needs no route.\n resetPassword: routes.resetPassword ?? '/',\n afterSignIn: routes.afterSignIn ?? '/',\n }\n}\n\nexport function resolveOAuth(oauth: AuthOAuthConfig = {}): Required<AuthOAuthConfig> {\n return {\n enabled: oauth.enabled ?? false,\n providers: oauth.providers ?? ['google'],\n comingSoon: oauth.comingSoon ?? ['google'],\n }\n}\n\nconst SIGNUP_HASH = /^#\\/?signup\\/?$/i\n\n/** `#/signup` is a marketable URL: a campaign link lands on the sign-up form. */\nexport function isSignupHash(hash: string): boolean {\n return SIGNUP_HASH.test(hash)\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/components/form-classes.ts
|
|
2
|
+
function fieldClass() {
|
|
3
|
+
return "h-10 w-full rounded-input border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50";
|
|
4
|
+
}
|
|
5
|
+
function buttonClass(variant = "primary") {
|
|
6
|
+
if (variant === "outline") {
|
|
7
|
+
return "inline-flex h-10 w-full items-center justify-center rounded-input border border-input bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50";
|
|
8
|
+
}
|
|
9
|
+
if (variant === "ghost") return "text-sm font-medium text-primary hover:underline";
|
|
10
|
+
return "inline-flex h-10 w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { buttonClass, fieldClass };
|
|
14
|
+
//# sourceMappingURL=chunk-55RFOMW5.js.map
|
|
15
|
+
//# sourceMappingURL=chunk-55RFOMW5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/form-classes.ts"],"names":[],"mappings":";AAAO,SAAS,UAAA,GAAa;AAC3B,EAAA,OAAO,yKAAA;AACT;AAEO,SAAS,WAAA,CAAY,UAA2C,SAAA,EAAW;AAChF,EAAA,IAAI,YAAY,SAAA,EAAW;AACzB,IAAA,OAAO,qNAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAA,KAAY,SAAS,OAAO,kDAAA;AAChC,EAAA,OAAO,wNAAA;AACT","file":"chunk-55RFOMW5.js","sourcesContent":["export function fieldClass() {\n return 'h-10 w-full rounded-input border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50'\n}\n\nexport function buttonClass(variant: 'primary' | 'outline' | 'ghost' = 'primary') {\n if (variant === 'outline') {\n return 'inline-flex h-10 w-full items-center justify-center rounded-input border border-input bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50'\n }\n if (variant === 'ghost') return 'text-sm font-medium text-primary hover:underline'\n return 'inline-flex h-10 w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50'\n}\n"]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { LoginForm } from './chunk-W35GQEGU.js';
|
|
2
|
+
import { SignupForm } from './chunk-HKILBDG2.js';
|
|
3
|
+
import { VerifyEmailNotice } from './chunk-QJRTW7Q3.js';
|
|
4
|
+
import { isSignupHash } from './chunk-4HVM2UJS.js';
|
|
5
|
+
import { ForgotPasswordForm } from './chunk-W7565FXD.js';
|
|
6
|
+
import { useAuth } from './chunk-Q6DFI35B.js';
|
|
7
|
+
import { AmbassadorBubbles } from './chunk-REU4D5UF.js';
|
|
8
|
+
import { TrialPromise } from './chunk-SMFXGZN7.js';
|
|
9
|
+
import * as React from 'react';
|
|
10
|
+
import { tl } from '@fayz-ai/core';
|
|
11
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
12
|
+
|
|
13
|
+
function initialViewFromLocation() {
|
|
14
|
+
if (typeof window === "undefined") return "login";
|
|
15
|
+
return isSignupHash(window.location.hash) ? "signup" : "login";
|
|
16
|
+
}
|
|
17
|
+
function LoginPage({
|
|
18
|
+
appName = "App",
|
|
19
|
+
logo,
|
|
20
|
+
loginLogo,
|
|
21
|
+
layout = "split",
|
|
22
|
+
tagline,
|
|
23
|
+
description,
|
|
24
|
+
loginAmbassadors,
|
|
25
|
+
loginAmbassadorsLabel,
|
|
26
|
+
showOAuth,
|
|
27
|
+
oauthProviders,
|
|
28
|
+
oauthComingSoon,
|
|
29
|
+
resetPasswordUrl,
|
|
30
|
+
trialDays,
|
|
31
|
+
termsUrl,
|
|
32
|
+
privacyUrl,
|
|
33
|
+
onSuccess
|
|
34
|
+
}) {
|
|
35
|
+
const [view, setView] = React.useState(initialViewFromLocation);
|
|
36
|
+
React.useEffect(() => {
|
|
37
|
+
if (isSignupHash(window.location.hash)) {
|
|
38
|
+
window.history.replaceState(null, "", window.location.pathname + window.location.search);
|
|
39
|
+
}
|
|
40
|
+
}, []);
|
|
41
|
+
const [email, setEmail] = React.useState("");
|
|
42
|
+
const pendingVerification = useAuth().pendingVerification;
|
|
43
|
+
const brandLogo = loginLogo ?? logo;
|
|
44
|
+
const heading = view === "login" ? tl("Welcome back", "Bem-vindo de volta") : view === "signup" ? tl("Create account", "Criar conta") : tl("Reset password", "Redefinir senha");
|
|
45
|
+
const subtitle = view === "login" ? tl("Enter your credentials to continue", "Entre com seus dados para continuar") : view === "signup" ? tl("Create your account to get started", "Crie sua conta para come\xE7ar") : tl(
|
|
46
|
+
"Enter your email and we will send reset instructions",
|
|
47
|
+
"Informe seu e-mail e enviaremos as instru\xE7\xF5es"
|
|
48
|
+
);
|
|
49
|
+
const form = view === "login" ? /* @__PURE__ */ jsx(
|
|
50
|
+
LoginForm,
|
|
51
|
+
{
|
|
52
|
+
onSuccess,
|
|
53
|
+
onForgotPassword: () => setView("recovery"),
|
|
54
|
+
showOAuth,
|
|
55
|
+
oauthProviders,
|
|
56
|
+
oauthComingSoon,
|
|
57
|
+
initialEmail: email,
|
|
58
|
+
onEmailChange: setEmail
|
|
59
|
+
}
|
|
60
|
+
) : view === "signup" ? /* @__PURE__ */ jsx(
|
|
61
|
+
SignupForm,
|
|
62
|
+
{
|
|
63
|
+
onSuccess,
|
|
64
|
+
showOAuth,
|
|
65
|
+
oauthProviders,
|
|
66
|
+
oauthComingSoon,
|
|
67
|
+
termsUrl,
|
|
68
|
+
privacyUrl
|
|
69
|
+
}
|
|
70
|
+
) : /* @__PURE__ */ jsx(
|
|
71
|
+
ForgotPasswordForm,
|
|
72
|
+
{
|
|
73
|
+
redirectTo: resetPasswordUrl,
|
|
74
|
+
onBackToLogin: () => setView("login"),
|
|
75
|
+
initialEmail: email,
|
|
76
|
+
onEmailChange: setEmail
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
const switcher = view === "login" ? /* @__PURE__ */ jsxs("p", { className: "text-center text-sm text-muted-foreground", children: [
|
|
80
|
+
tl("Do not have an account?", "Ainda n\xE3o tem conta?"),
|
|
81
|
+
" ",
|
|
82
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "font-medium text-primary hover:underline", onClick: () => setView("signup"), children: tl("Sign up", "Cadastre-se") })
|
|
83
|
+
] }) : /* @__PURE__ */ jsxs("p", { className: "text-center text-sm text-muted-foreground", children: [
|
|
84
|
+
view === "signup" ? tl("Already have an account?", "J\xE1 tem uma conta?") : tl("Remembered your password?", "Lembrou a senha?"),
|
|
85
|
+
" ",
|
|
86
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "font-medium text-primary hover:underline", onClick: () => setView("login"), children: tl("Sign in", "Entrar") })
|
|
87
|
+
] });
|
|
88
|
+
const panel = pendingVerification ? /* @__PURE__ */ jsx(
|
|
89
|
+
VerifyEmailNotice,
|
|
90
|
+
{
|
|
91
|
+
email: pendingVerification.email,
|
|
92
|
+
onUseAnotherEmail: () => setView("signup")
|
|
93
|
+
}
|
|
94
|
+
) : null;
|
|
95
|
+
if (layout === "centered") {
|
|
96
|
+
return /* @__PURE__ */ jsx("div", { className: "flex min-h-screen items-center justify-center bg-sidebar p-4", children: /* @__PURE__ */ jsx("div", { className: "w-full max-w-md rounded-2xl border border-border/50 bg-card p-8 shadow-2xl sm:p-10", children: panel ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
97
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-8 text-center", children: [
|
|
98
|
+
brandLogo && /* @__PURE__ */ jsx("div", { className: "mb-4 flex justify-center", children: brandLogo }),
|
|
99
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-foreground", children: heading }),
|
|
100
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: subtitle }),
|
|
101
|
+
view === "signup" && trialDays ? /* @__PURE__ */ jsx(TrialPromise, { days: trialDays }) : null
|
|
102
|
+
] }),
|
|
103
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
104
|
+
form,
|
|
105
|
+
switcher
|
|
106
|
+
] })
|
|
107
|
+
] }) }) });
|
|
108
|
+
}
|
|
109
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen bg-sidebar", children: [
|
|
110
|
+
/* @__PURE__ */ jsxs("div", { className: "relative hidden w-1/2 overflow-hidden lg:block", children: [
|
|
111
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-br from-sidebar-accent/50 via-transparent to-sidebar/80" }),
|
|
112
|
+
/* @__PURE__ */ jsx("div", { className: "relative z-20 flex h-full flex-col justify-end p-12", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
113
|
+
/* @__PURE__ */ jsx(AmbassadorBubbles, { ambassadors: loginAmbassadors, label: loginAmbassadorsLabel }),
|
|
114
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-sidebar-muted", children: appName }),
|
|
115
|
+
/* @__PURE__ */ jsx("h2", { className: "text-3xl font-bold text-sidebar-foreground", children: tagline ?? `Welcome to ${appName}` }),
|
|
116
|
+
description && /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm leading-relaxed text-sidebar-muted", children: description })
|
|
117
|
+
] }) })
|
|
118
|
+
] }),
|
|
119
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col lg:p-3 lg:pl-3", children: /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col justify-center bg-background px-6 py-12 lg:rounded-[1.25rem] lg:px-16", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-sm", children: [
|
|
120
|
+
brandLogo && /* @__PURE__ */ jsx("div", { className: "mb-8", children: brandLogo }),
|
|
121
|
+
panel ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
122
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-8", children: [
|
|
123
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-foreground", children: heading }),
|
|
124
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: subtitle }),
|
|
125
|
+
view === "signup" && trialDays ? /* @__PURE__ */ jsx(TrialPromise, { days: trialDays }) : null
|
|
126
|
+
] }),
|
|
127
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
128
|
+
form,
|
|
129
|
+
switcher
|
|
130
|
+
] })
|
|
131
|
+
] })
|
|
132
|
+
] }) }) })
|
|
133
|
+
] });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { LoginPage };
|
|
137
|
+
//# sourceMappingURL=chunk-7JYNJQJJ.js.map
|
|
138
|
+
//# sourceMappingURL=chunk-7JYNJQJJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/views/LoginPage.tsx"],"names":[],"mappings":"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","file":"chunk-7JYNJQJJ.js","sourcesContent":["import * as React from 'react'\nimport { tl } from '@fayz-ai/core'\nimport { useAuth } from '../context'\nimport { isSignupHash, type AuthFormView, type AuthLayout, type LoginAmbassador, type OAuthProviderKey } from '../domain/auth-config'\nimport { AmbassadorBubbles } from '../components/AmbassadorBubbles'\nimport { TrialPromise } from '../components/TrialPromise'\nimport { ForgotPasswordForm } from './ForgotPasswordForm'\nimport { LoginForm } from './LoginForm'\nimport { SignupForm } from './SignupForm'\nimport { VerifyEmailNotice } from './VerifyEmailNotice'\n\nexport interface LoginPageProps {\n appName?: string\n logo?: React.ReactNode\n loginLogo?: React.ReactNode\n layout?: AuthLayout\n tagline?: string\n description?: string\n /** Product ambassadors shown as a small social-proof avatar strip above the\n * tagline on the split-layout brand panel. Renders nothing when empty/imageless. */\n loginAmbassadors?: LoginAmbassador[]\n /** Optional microtext beside the ambassador strip, e.g. \"+2 mil profissionais\". */\n loginAmbassadorsLabel?: string\n showOAuth?: boolean\n oauthProviders?: OAuthProviderKey[]\n /** Providers shown as \"em breve\" — declared but not wired yet. */\n oauthComingSoon?: OAuthProviderKey[]\n resetPasswordUrl?: string\n /**\n * Length of the free trial, in days, as the sign-up screen promises it.\n *\n * Display only — the window itself is stamped by the database when the\n * workspace is created. Absent means the app has no trial to offer, and the\n * screen says nothing rather than inventing one.\n */\n trialDays?: number\n /** Shown under the sign-up button when the app has them. */\n termsUrl?: string\n privacyUrl?: string\n onSuccess?: () => void\n}\n\n// Pure read: StrictMode runs state initializers twice, so consuming the hash here would land the second pass on login.\nfunction initialViewFromLocation(): AuthFormView {\n if (typeof window === 'undefined') return 'login'\n return isSignupHash(window.location.hash) ? 'signup' : 'login'\n}\n\nexport function LoginPage({\n appName = 'App',\n logo,\n loginLogo,\n layout = 'split',\n tagline,\n description,\n loginAmbassadors,\n loginAmbassadorsLabel,\n showOAuth,\n oauthProviders,\n oauthComingSoon,\n resetPasswordUrl,\n trialDays,\n termsUrl,\n privacyUrl,\n onSuccess,\n}: LoginPageProps) {\n const [view, setView] = React.useState<AuthFormView>(initialViewFromLocation)\n // The hash did its job at mount; cleared here so the app router never sees a\n // route that does not exist once the session begins.\n React.useEffect(() => {\n if (isSignupHash(window.location.hash)) {\n window.history.replaceState(null, '', window.location.pathname + window.location.search)\n }\n }, [])\n // Lifted out of the forms so switching view carries the e-mail across: someone\n // who typed it and then remembered they forgot the password should not have to\n // type it again (and back on the login form it is still there).\n const [email, setEmail] = React.useState('')\n // A sign-up that produced no session is waiting on a confirmation link, and\n // the notice replaces the whole panel until the link is clicked or abandoned.\n const pendingVerification = useAuth().pendingVerification\n const brandLogo = loginLogo ?? logo\n const heading =\n view === 'login'\n ? tl('Welcome back', 'Bem-vindo de volta')\n : view === 'signup'\n ? tl('Create account', 'Criar conta')\n : tl('Reset password', 'Redefinir senha')\n const subtitle =\n view === 'login'\n ? tl('Enter your credentials to continue', 'Entre com seus dados para continuar')\n : view === 'signup'\n ? tl('Create your account to get started', 'Crie sua conta para começar')\n : tl(\n 'Enter your email and we will send reset instructions',\n 'Informe seu e-mail e enviaremos as instruções',\n )\n const form =\n view === 'login' ? (\n <LoginForm\n onSuccess={onSuccess}\n onForgotPassword={() => setView('recovery')}\n showOAuth={showOAuth}\n oauthProviders={oauthProviders}\n oauthComingSoon={oauthComingSoon}\n initialEmail={email}\n onEmailChange={setEmail}\n />\n ) : view === 'signup' ? (\n <SignupForm\n onSuccess={onSuccess}\n showOAuth={showOAuth}\n oauthProviders={oauthProviders}\n oauthComingSoon={oauthComingSoon}\n termsUrl={termsUrl}\n privacyUrl={privacyUrl}\n />\n ) : (\n <ForgotPasswordForm\n redirectTo={resetPasswordUrl}\n onBackToLogin={() => setView('login')}\n initialEmail={email}\n onEmailChange={setEmail}\n />\n )\n const switcher =\n view === 'login' ? (\n <p className=\"text-center text-sm text-muted-foreground\">\n {tl('Do not have an account?', 'Ainda não tem conta?')}{' '}\n <button type=\"button\" className=\"font-medium text-primary hover:underline\" onClick={() => setView('signup')}>\n {tl('Sign up', 'Cadastre-se')}\n </button>\n </p>\n ) : (\n <p className=\"text-center text-sm text-muted-foreground\">\n {view === 'signup'\n ? tl('Already have an account?', 'Já tem uma conta?')\n : tl('Remembered your password?', 'Lembrou a senha?')}{' '}\n <button type=\"button\" className=\"font-medium text-primary hover:underline\" onClick={() => setView('login')}>\n {tl('Sign in', 'Entrar')}\n </button>\n </p>\n )\n\n // The notice owns the panel: heading, form and switcher would all be talking\n // about a step the user has already finished.\n const panel = pendingVerification ? (\n <VerifyEmailNotice\n email={pendingVerification.email}\n onUseAnotherEmail={() => setView('signup')}\n />\n ) : null\n\n if (layout === 'centered') {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-sidebar p-4\">\n <div className=\"w-full max-w-md rounded-2xl border border-border/50 bg-card p-8 shadow-2xl sm:p-10\">\n {panel ?? (\n <>\n <div className=\"mb-8 text-center\">\n {brandLogo && <div className=\"mb-4 flex justify-center\">{brandLogo}</div>}\n <h1 className=\"text-2xl font-bold text-foreground\">{heading}</h1>\n <p className=\"mt-1 text-sm text-muted-foreground\">{subtitle}</p>\n {view === 'signup' && trialDays ? <TrialPromise days={trialDays} /> : null}\n </div>\n <div className=\"space-y-6\">\n {form}\n {switcher}\n </div>\n </>\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"flex min-h-screen bg-sidebar\">\n <div className=\"relative hidden w-1/2 overflow-hidden lg:block\">\n <div className=\"absolute inset-0 bg-gradient-to-br from-sidebar-accent/50 via-transparent to-sidebar/80\" />\n <div className=\"relative z-20 flex h-full flex-col justify-end p-12\">\n <div className=\"space-y-4\">\n <AmbassadorBubbles ambassadors={loginAmbassadors} label={loginAmbassadorsLabel} />\n <p className=\"text-sm font-medium text-sidebar-muted\">{appName}</p>\n <h2 className=\"text-3xl font-bold text-sidebar-foreground\">{tagline ?? `Welcome to ${appName}`}</h2>\n {description && <p className=\"max-w-md text-sm leading-relaxed text-sidebar-muted\">{description}</p>}\n </div>\n </div>\n </div>\n <div className=\"flex flex-1 flex-col lg:p-3 lg:pl-3\">\n <div className=\"flex flex-1 flex-col justify-center bg-background px-6 py-12 lg:rounded-[1.25rem] lg:px-16\">\n <div className=\"mx-auto w-full max-w-sm\">\n {brandLogo && <div className=\"mb-8\">{brandLogo}</div>}\n {panel ?? (\n <>\n <div className=\"mb-8\">\n <h1 className=\"text-2xl font-bold text-foreground\">{heading}</h1>\n <p className=\"mt-1 text-sm text-muted-foreground\">{subtitle}</p>\n {view === 'signup' && trialDays ? <TrialPromise days={trialDays} /> : null}\n </div>\n <div className=\"space-y-6\">\n {form}\n {switcher}\n </div>\n </>\n )}\n </div>\n </div>\n </div>\n </div>\n )\n}\n"]}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { withAuthIntent } from './chunk-2MO4QTQA.js';
|
|
2
|
+
import { createClient } from '@supabase/supabase-js';
|
|
3
|
+
import { isImpersonating } from '@fayz-ai/core';
|
|
4
|
+
|
|
5
|
+
var MUST_SET_PASSWORD_FLAG = "fayz_must_set_password";
|
|
6
|
+
function mapSupabaseUser(supabaseUser) {
|
|
7
|
+
return {
|
|
8
|
+
id: supabaseUser.id,
|
|
9
|
+
email: supabaseUser.email ?? "",
|
|
10
|
+
fullName: supabaseUser.user_metadata?.["full_name"] ?? supabaseUser.user_metadata?.["name"] ?? "",
|
|
11
|
+
avatarUrl: supabaseUser.user_metadata?.["avatar_url"],
|
|
12
|
+
// An admin viewing as this person must not be pushed into choosing THEIR password.
|
|
13
|
+
mustSetPassword: supabaseUser.user_metadata?.[MUST_SET_PASSWORD_FLAG] === true && !isImpersonating()
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function mapSupabaseSession(session) {
|
|
17
|
+
return {
|
|
18
|
+
accessToken: session.access_token,
|
|
19
|
+
refreshToken: session.refresh_token,
|
|
20
|
+
expiresAt: session.expires_at ?? Math.floor(Date.now() / 1e3) + (session.expires_in ?? 3600),
|
|
21
|
+
user: mapSupabaseUser(session.user)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function createSupabaseAuthAdapter(config) {
|
|
25
|
+
const supabase = config.supabaseClient ?? createClient(config.supabaseUrl, config.supabaseAnonKey);
|
|
26
|
+
return {
|
|
27
|
+
async getSession() {
|
|
28
|
+
const {
|
|
29
|
+
data: { session }
|
|
30
|
+
} = await supabase.auth.getSession();
|
|
31
|
+
if (!session) return null;
|
|
32
|
+
const mapped = mapSupabaseSession(session);
|
|
33
|
+
return { user: mapped.user, session: mapped };
|
|
34
|
+
},
|
|
35
|
+
onAuthStateChange(cb) {
|
|
36
|
+
const {
|
|
37
|
+
data: { subscription }
|
|
38
|
+
} = supabase.auth.onAuthStateChange((_event, session) => {
|
|
39
|
+
cb(session ? mapSupabaseUser(session.user) : null);
|
|
40
|
+
});
|
|
41
|
+
return () => subscription.unsubscribe();
|
|
42
|
+
},
|
|
43
|
+
async signIn(email, password) {
|
|
44
|
+
const { data, error } = await supabase.auth.signInWithPassword({ email, password });
|
|
45
|
+
if (error) throw error;
|
|
46
|
+
return mapSupabaseSession(data.session);
|
|
47
|
+
},
|
|
48
|
+
/**
|
|
49
|
+
* Adopt a session handed over by a sibling application (see url-intent).
|
|
50
|
+
*
|
|
51
|
+
* `setSession` is the verification, not a formality: GoTrue validates the
|
|
52
|
+
* tokens and rotates the refresh token, so a forged or replayed fragment
|
|
53
|
+
* fails here rather than being trusted because it looked well-formed.
|
|
54
|
+
*/
|
|
55
|
+
async adoptSession(accessToken, refreshToken) {
|
|
56
|
+
const { data, error } = await supabase.auth.setSession({
|
|
57
|
+
access_token: accessToken,
|
|
58
|
+
refresh_token: refreshToken
|
|
59
|
+
});
|
|
60
|
+
if (error || !data.session) return null;
|
|
61
|
+
return mapSupabaseSession(data.session);
|
|
62
|
+
},
|
|
63
|
+
async signUp(email, password, fullName) {
|
|
64
|
+
const { data, error } = await supabase.auth.signUp({
|
|
65
|
+
email,
|
|
66
|
+
password,
|
|
67
|
+
options: { data: { full_name: fullName } }
|
|
68
|
+
});
|
|
69
|
+
if (error) throw error;
|
|
70
|
+
if (!data.session) {
|
|
71
|
+
if (!data.user) throw new Error("Signup failed: no user returned");
|
|
72
|
+
return {
|
|
73
|
+
accessToken: "",
|
|
74
|
+
refreshToken: "",
|
|
75
|
+
expiresAt: 0,
|
|
76
|
+
user: mapSupabaseUser(data.user)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return mapSupabaseSession(data.session);
|
|
80
|
+
},
|
|
81
|
+
async resendConfirmation(email, options) {
|
|
82
|
+
const { error } = await supabase.auth.resend({
|
|
83
|
+
type: "signup",
|
|
84
|
+
email,
|
|
85
|
+
options: {
|
|
86
|
+
emailRedirectTo: options?.redirectTo ?? config.callbackUrl ?? (typeof window !== "undefined" ? window.location.origin : void 0)
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
if (error) throw error;
|
|
90
|
+
},
|
|
91
|
+
async signOut() {
|
|
92
|
+
const { error } = await supabase.auth.signOut();
|
|
93
|
+
if (error) throw error;
|
|
94
|
+
},
|
|
95
|
+
async signInWithOAuth(provider) {
|
|
96
|
+
const { error } = await supabase.auth.signInWithOAuth({
|
|
97
|
+
provider,
|
|
98
|
+
options: {
|
|
99
|
+
redirectTo: config.callbackUrl ?? (typeof window !== "undefined" ? `${window.location.origin}/auth/callback` : void 0)
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
if (error) throw error;
|
|
103
|
+
},
|
|
104
|
+
async resetPassword(email, options) {
|
|
105
|
+
const explicit = options?.redirectTo ?? config.resetPasswordUrl;
|
|
106
|
+
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
|
107
|
+
redirectTo: explicit ? withAuthIntent(explicit, "recovery") : typeof window !== "undefined" ? `${window.location.origin}/` : void 0
|
|
108
|
+
});
|
|
109
|
+
if (error) throw error;
|
|
110
|
+
},
|
|
111
|
+
async updatePassword(password) {
|
|
112
|
+
const { data, error } = await supabase.auth.updateUser({
|
|
113
|
+
password,
|
|
114
|
+
data: { [MUST_SET_PASSWORD_FLAG]: false }
|
|
115
|
+
});
|
|
116
|
+
if (error) throw error;
|
|
117
|
+
const {
|
|
118
|
+
data: { session }
|
|
119
|
+
} = await supabase.auth.getSession();
|
|
120
|
+
if (session) return mapSupabaseSession(session);
|
|
121
|
+
if (!data.user) return void 0;
|
|
122
|
+
return {
|
|
123
|
+
accessToken: "",
|
|
124
|
+
refreshToken: "",
|
|
125
|
+
expiresAt: 0,
|
|
126
|
+
user: mapSupabaseUser(data.user)
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
async handleCallback(url) {
|
|
130
|
+
const target = url ?? (typeof window !== "undefined" ? window.location.href : "");
|
|
131
|
+
const parsed = new URL(target);
|
|
132
|
+
const fragment = new URLSearchParams(parsed.hash.replace(/^#/, ""));
|
|
133
|
+
const param = (key) => parsed.searchParams.get(key) ?? fragment.get(key);
|
|
134
|
+
const code = param("code");
|
|
135
|
+
if (code) {
|
|
136
|
+
const { data, error } = await supabase.auth.exchangeCodeForSession(code);
|
|
137
|
+
if (error) throw error;
|
|
138
|
+
return data.session ? mapSupabaseSession(data.session) : null;
|
|
139
|
+
}
|
|
140
|
+
const tokenHash = param("token_hash") ?? param("token");
|
|
141
|
+
const type = param("type");
|
|
142
|
+
if (tokenHash && type) {
|
|
143
|
+
const { data, error } = await supabase.auth.verifyOtp({
|
|
144
|
+
token_hash: tokenHash,
|
|
145
|
+
type
|
|
146
|
+
});
|
|
147
|
+
if (error) throw error;
|
|
148
|
+
return data.session ? mapSupabaseSession(data.session) : null;
|
|
149
|
+
}
|
|
150
|
+
const {
|
|
151
|
+
data: { session }
|
|
152
|
+
} = await supabase.auth.getSession();
|
|
153
|
+
return session ? mapSupabaseSession(session) : null;
|
|
154
|
+
},
|
|
155
|
+
async inviteUser(email, options) {
|
|
156
|
+
const { error } = await supabase.auth.signInWithOtp({
|
|
157
|
+
email,
|
|
158
|
+
options: {
|
|
159
|
+
shouldCreateUser: true,
|
|
160
|
+
emailRedirectTo: options?.redirectTo ?? (typeof window !== "undefined" ? `${window.location.origin}/` : void 0),
|
|
161
|
+
// `data` only lands on users the OTP actually creates, which is exactly
|
|
162
|
+
// who needs a password: the shell holds them on the set-password screen
|
|
163
|
+
// until they choose one, so they can sign in normally from then on.
|
|
164
|
+
data: { ...options?.data, [MUST_SET_PASSWORD_FLAG]: true }
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
if (error) throw error;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export { MUST_SET_PASSWORD_FLAG, createSupabaseAuthAdapter };
|
|
173
|
+
//# sourceMappingURL=chunk-BGCCXJJA.js.map
|
|
174
|
+
//# sourceMappingURL=chunk-BGCCXJJA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/supabase.ts"],"names":[],"mappings":"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","file":"chunk-BGCCXJJA.js","sourcesContent":["import { createClient, type SupabaseClient } from '@supabase/supabase-js'\nimport type { AuthAdapter, AuthUser, AuthSession, AuthProvider } from '@fayz-ai/core'\nimport { isImpersonating } from '@fayz-ai/core'\nimport { withAuthIntent } from '../lib/url-intent'\n\n/** Metadata flag stamped on invited users (created by a magic link, so they have\n * no password yet) and cleared the moment they choose one. Metadata, not a URL\n * param: it survives the link being opened later, in another browser, or twice —\n * and it needs no new entry in Supabase's redirect allow-list. */\nexport const MUST_SET_PASSWORD_FLAG = 'fayz_must_set_password'\n\nexport interface SupabaseAuthConfig {\n supabaseUrl: string\n supabaseAnonKey: string\n supabaseClient?: SupabaseClient\n callbackUrl?: string\n resetPasswordUrl?: string\n}\n\nfunction mapSupabaseUser(supabaseUser: {\n id: string\n email?: string | null\n created_at?: string\n updated_at?: string | null\n email_confirmed_at?: string | null\n user_metadata?: Record<string, unknown>\n app_metadata?: { providers?: string[]; provider?: string }\n}): AuthUser {\n return {\n id: supabaseUser.id,\n email: supabaseUser.email ?? '',\n fullName:\n (supabaseUser.user_metadata?.['full_name'] as string | undefined) ??\n (supabaseUser.user_metadata?.['name'] as string | undefined) ??\n '',\n avatarUrl: supabaseUser.user_metadata?.['avatar_url'] as string | undefined,\n // An admin viewing as this person must not be pushed into choosing THEIR password.\n mustSetPassword: supabaseUser.user_metadata?.[MUST_SET_PASSWORD_FLAG] === true && !isImpersonating(),\n }\n}\n\nfunction mapSupabaseSession(session: {\n access_token: string\n refresh_token?: string\n expires_at?: number\n expires_in?: number\n user: Parameters<typeof mapSupabaseUser>[0]\n}): AuthSession {\n return {\n accessToken: session.access_token,\n refreshToken: session.refresh_token,\n expiresAt:\n session.expires_at ??\n Math.floor(Date.now() / 1000) + (session.expires_in ?? 3600),\n user: mapSupabaseUser(session.user),\n }\n}\n\nexport function createSupabaseAuthAdapter(config: SupabaseAuthConfig): AuthAdapter {\n const supabase = config.supabaseClient ?? createClient(config.supabaseUrl, config.supabaseAnonKey)\n\n return {\n async getSession() {\n const {\n data: { session },\n } = await supabase.auth.getSession()\n if (!session) return null\n const mapped = mapSupabaseSession(session)\n return { user: mapped.user, session: mapped }\n },\n\n onAuthStateChange(cb) {\n const {\n data: { subscription },\n } = supabase.auth.onAuthStateChange((_event, session) => {\n cb(session ? mapSupabaseUser(session.user) : null)\n })\n return () => subscription.unsubscribe()\n },\n\n async signIn(email, password) {\n const { data, error } = await supabase.auth.signInWithPassword({ email, password })\n if (error) throw error\n return mapSupabaseSession(data.session)\n },\n\n /**\n * Adopt a session handed over by a sibling application (see url-intent).\n *\n * `setSession` is the verification, not a formality: GoTrue validates the\n * tokens and rotates the refresh token, so a forged or replayed fragment\n * fails here rather than being trusted because it looked well-formed.\n */\n async adoptSession(accessToken: string, refreshToken: string) {\n const { data, error } = await supabase.auth.setSession({\n access_token: accessToken,\n refresh_token: refreshToken,\n })\n if (error || !data.session) return null\n return mapSupabaseSession(data.session)\n },\n\n async signUp(email, password, fullName) {\n const { data, error } = await supabase.auth.signUp({\n email,\n password,\n options: { data: { full_name: fullName } },\n })\n if (error) throw error\n\n // When email confirmation is required, session is null\n if (!data.session) {\n if (!data.user) throw new Error('Signup failed: no user returned')\n return {\n accessToken: '',\n refreshToken: '',\n expiresAt: 0,\n user: mapSupabaseUser(data.user),\n }\n }\n\n return mapSupabaseSession(data.session)\n },\n\n async resendConfirmation(email: string, options?: { redirectTo?: string }) {\n const { error } = await supabase.auth.resend({\n type: 'signup',\n email,\n options: {\n emailRedirectTo:\n options?.redirectTo ??\n config.callbackUrl ??\n (typeof window !== 'undefined' ? window.location.origin : undefined),\n },\n })\n if (error) throw error\n },\n\n async signOut() {\n const { error } = await supabase.auth.signOut()\n if (error) throw error\n },\n\n async signInWithOAuth(provider: AuthProvider) {\n const { error } = await supabase.auth.signInWithOAuth({\n provider: provider as 'google' | 'github' | 'apple',\n options: {\n redirectTo:\n config.callbackUrl ??\n (typeof window !== 'undefined' ? `${window.location.origin}/auth/callback` : undefined),\n },\n })\n if (error) throw error\n },\n\n async resetPassword(email, options) {\n // Root, not `/auth/reset-password`: the shell recognises a recovery link by\n // the link's own `type=recovery` param, so the flow needs no route of its\n // own — and the app root is the one URL every deployment already serves and\n // every Supabase project already allow-lists (a URL outside the allow-list\n // is silently swapped for the project's Site URL, i.e. another app).\n // A host that DOES point recovery somewhere else gets the explicit intent\n // marker, since we cannot assume its landing page carries the params.\n const explicit = options?.redirectTo ?? config.resetPasswordUrl\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: explicit\n ? withAuthIntent(explicit, 'recovery')\n : typeof window !== 'undefined'\n ? `${window.location.origin}/`\n : undefined,\n })\n if (error) throw error\n },\n\n async updatePassword(password) {\n // Clearing the flag in the same call as the password: one round-trip, and\n // no window where the password is set but the screen still demands one.\n const { data, error } = await supabase.auth.updateUser({\n password,\n data: { [MUST_SET_PASSWORD_FLAG]: false },\n })\n if (error) throw error\n const {\n data: { session },\n } = await supabase.auth.getSession()\n if (session) return mapSupabaseSession(session)\n if (!data.user) return undefined\n return {\n accessToken: '',\n refreshToken: '',\n expiresAt: 0,\n user: mapSupabaseUser(data.user),\n }\n },\n\n async handleCallback(url) {\n const target = url ?? (typeof window !== 'undefined' ? window.location.href : '')\n const parsed = new URL(target)\n // The fragment carries the params on the implicit flow; the query on PKCE\n // and on `{{ .TokenHash }}`-style e-mail templates.\n const fragment = new URLSearchParams(parsed.hash.replace(/^#/, ''))\n const param = (key: string) => parsed.searchParams.get(key) ?? fragment.get(key)\n\n const code = param('code')\n if (code) {\n const { data, error } = await supabase.auth.exchangeCodeForSession(code)\n if (error) throw error\n return data.session ? mapSupabaseSession(data.session) : null\n }\n\n // Templates that link straight at the app with a token instead of routing\n // through GoTrue's /verify never produce a session on their own.\n const tokenHash = param('token_hash') ?? param('token')\n const type = param('type')\n if (tokenHash && type) {\n const { data, error } = await supabase.auth.verifyOtp({\n token_hash: tokenHash,\n type: type as 'recovery' | 'invite' | 'magiclink' | 'signup' | 'email',\n })\n if (error) throw error\n return data.session ? mapSupabaseSession(data.session) : null\n }\n\n const {\n data: { session },\n } = await supabase.auth.getSession()\n return session ? mapSupabaseSession(session) : null\n },\n\n async inviteUser(email, options) {\n // Native, edge-function-free invite: a passwordless magic link. Sends the\n // e-mail + creates the auth user (anon-callable, no service-role/admin).\n // Membership is NOT granted from `data` (a client could forge it) — the\n // accept-time trigger trusts the RLS-protected public.invitations row.\n const { error } = await supabase.auth.signInWithOtp({\n email,\n options: {\n shouldCreateUser: true,\n emailRedirectTo:\n options?.redirectTo ??\n (typeof window !== 'undefined' ? `${window.location.origin}/` : undefined),\n // `data` only lands on users the OTP actually creates, which is exactly\n // who needs a password: the shell holds them on the set-password screen\n // until they choose one, so they can sign in normally from then on.\n data: { ...options?.data, [MUST_SET_PASSWORD_FLAG]: true },\n },\n })\n if (error) throw error\n },\n }\n}\n"]}
|