@fayz-ai/auth 0.9.0-next.0 → 0.9.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.
@@ -1,5 +1,10 @@
1
1
  import { type SupabaseClient } from '@supabase/supabase-js';
2
2
  import type { AuthAdapter } from '@fayz-ai/core';
3
+ /** Metadata flag stamped on invited users (created by a magic link, so they have
4
+ * no password yet) and cleared the moment they choose one. Metadata, not a URL
5
+ * param: it survives the link being opened later, in another browser, or twice —
6
+ * and it needs no new entry in Supabase's redirect allow-list. */
7
+ export declare const MUST_SET_PASSWORD_FLAG = "fayz_must_set_password";
3
8
  export interface SupabaseAuthConfig {
4
9
  supabaseUrl: string;
5
10
  supabaseAnonKey: string;
@@ -1 +1 @@
1
- {"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/adapters/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACzE,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,eAAe,CAAA;AAErF,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAuCD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CA6HjF"}
1
+ {"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/adapters/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACzE,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,eAAe,CAAA;AAGrF;;;mEAGmE;AACnE,eAAO,MAAM,sBAAsB,2BAA2B,CAAA;AAE9D,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAwCD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAkKjF"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- export { createSupabaseAuthAdapter } from './adapters/supabase';
1
+ export { createSupabaseAuthAdapter, MUST_SET_PASSWORD_FLAG } from './adapters/supabase';
2
2
  export type { SupabaseAuthConfig } from './adapters/supabase';
3
+ export { readAuthUrlIntent, clearAuthUrlTokens, withAuthIntent, decidePasswordSetup, AUTH_INTENT_PARAM, } from './url-intent';
4
+ export type { AuthUrlIntent, PasswordSetupDecision, PasswordSetupInput, PasswordSetupReason, } from './url-intent';
3
5
  export { createMockAuthAdapter } from './adapters/mock';
4
6
  export type { MockUser } from './adapters/mock';
5
7
  export { useAuthStore } from './store';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAE7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACvD,YAAY,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAG/C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAGnD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACvF,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAG7D,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,aAAa,EACb,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACvD,YAAY,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAG/C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAGnD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA"}
package/dist/index.js CHANGED
@@ -4,12 +4,78 @@ import { createContext, useRef, useEffect, useContext } from 'react';
4
4
  import { jsx } from 'react/jsx-runtime';
5
5
 
6
6
  // src/adapters/supabase.ts
7
+
8
+ // src/url-intent.ts
9
+ var AUTH_INTENT_PARAM = "fayz_auth";
10
+ var RESET_PATH = "/auth/reset-password";
11
+ function snapshot() {
12
+ if (typeof window === "undefined") return null;
13
+ return {
14
+ pathname: window.location.pathname,
15
+ search: window.location.search,
16
+ hash: window.location.hash
17
+ };
18
+ }
19
+ var initialUrl = snapshot();
20
+ function hashParams(hash) {
21
+ const raw = hash.replace(/^#/, "");
22
+ if (!raw || !raw.includes("=")) return new URLSearchParams();
23
+ return new URLSearchParams(raw);
24
+ }
25
+ function readAuthUrlIntent(url) {
26
+ const loc = url ?? initialUrl;
27
+ if (!loc) return null;
28
+ const query = new URLSearchParams(loc.search);
29
+ const hash = hashParams(loc.hash);
30
+ const param = (key) => hash.get(key) ?? query.get(key);
31
+ const errorCode = param("error_code") ?? param("error");
32
+ const errorDescription = param("error_description")?.replace(/\+/g, " ");
33
+ if (errorCode && /otp|expired|link/i.test(`${errorCode} ${errorDescription ?? ""}`)) {
34
+ return { kind: "expired", message: errorDescription ?? void 0 };
35
+ }
36
+ const marker = query.get(AUTH_INTENT_PARAM);
37
+ const type = param("type");
38
+ if (type === "recovery" || marker === "recovery") return { kind: "recovery" };
39
+ const path = loc.pathname.replace(/\/+$/, "");
40
+ if (path.endsWith(RESET_PATH)) return { kind: "recovery" };
41
+ return null;
42
+ }
43
+ function clearAuthUrlTokens() {
44
+ if (typeof window === "undefined") return;
45
+ const url = new URL(window.location.href);
46
+ url.searchParams.delete(AUTH_INTENT_PARAM);
47
+ if (url.hash.includes("=")) url.hash = "";
48
+ window.history.replaceState(window.history.state, "", url.toString());
49
+ }
50
+ function withAuthIntent(url, intent) {
51
+ const base = typeof window === "undefined" ? "http://localhost" : window.location.origin;
52
+ const parsed = new URL(url, base);
53
+ parsed.searchParams.set(AUTH_INTENT_PARAM, intent);
54
+ return /^https?:\/\//i.test(url) ? parsed.toString() : `${parsed.pathname}${parsed.search}${parsed.hash}`;
55
+ }
56
+ var NOTHING = { reason: null, expired: false, checking: false };
57
+ function decidePasswordSetup(input) {
58
+ const { intent, isLoading, isAuthenticated, mustSetPassword } = input;
59
+ if (intent?.kind === "expired") return { reason: "recovery", expired: true, checking: false };
60
+ if (intent?.kind === "recovery") {
61
+ if (isLoading) return { reason: "recovery", expired: false, checking: true };
62
+ return { reason: "recovery", expired: !isAuthenticated, checking: false };
63
+ }
64
+ if (!isLoading && isAuthenticated && mustSetPassword) {
65
+ return { reason: "invite", expired: false, checking: false };
66
+ }
67
+ return NOTHING;
68
+ }
69
+
70
+ // src/adapters/supabase.ts
71
+ var MUST_SET_PASSWORD_FLAG = "fayz_must_set_password";
7
72
  function mapSupabaseUser(supabaseUser) {
8
73
  return {
9
74
  id: supabaseUser.id,
10
75
  email: supabaseUser.email ?? "",
11
76
  fullName: supabaseUser.user_metadata?.["full_name"] ?? supabaseUser.user_metadata?.["name"] ?? "",
12
- avatarUrl: supabaseUser.user_metadata?.["avatar_url"]
77
+ avatarUrl: supabaseUser.user_metadata?.["avatar_url"],
78
+ mustSetPassword: supabaseUser.user_metadata?.[MUST_SET_PASSWORD_FLAG] === true
13
79
  };
14
80
  }
15
81
  function mapSupabaseSession(session) {
@@ -76,13 +142,17 @@ function createSupabaseAuthAdapter(config) {
76
142
  if (error) throw error;
77
143
  },
78
144
  async resetPassword(email, options) {
145
+ const explicit = options?.redirectTo ?? config.resetPasswordUrl;
79
146
  const { error } = await supabase.auth.resetPasswordForEmail(email, {
80
- redirectTo: options?.redirectTo ?? config.resetPasswordUrl ?? (typeof window !== "undefined" ? `${window.location.origin}/auth/reset-password` : void 0)
147
+ redirectTo: explicit ? withAuthIntent(explicit, "recovery") : typeof window !== "undefined" ? `${window.location.origin}/` : void 0
81
148
  });
82
149
  if (error) throw error;
83
150
  },
84
151
  async updatePassword(password) {
85
- const { data, error } = await supabase.auth.updateUser({ password });
152
+ const { data, error } = await supabase.auth.updateUser({
153
+ password,
154
+ data: { [MUST_SET_PASSWORD_FLAG]: false }
155
+ });
86
156
  if (error) throw error;
87
157
  const {
88
158
  data: { session }
@@ -98,12 +168,25 @@ function createSupabaseAuthAdapter(config) {
98
168
  },
99
169
  async handleCallback(url) {
100
170
  const target = url ?? (typeof window !== "undefined" ? window.location.href : "");
101
- const code = new URL(target).searchParams.get("code");
171
+ const parsed = new URL(target);
172
+ const fragment = new URLSearchParams(parsed.hash.replace(/^#/, ""));
173
+ const param = (key) => parsed.searchParams.get(key) ?? fragment.get(key);
174
+ const code = param("code");
102
175
  if (code) {
103
176
  const { data, error } = await supabase.auth.exchangeCodeForSession(code);
104
177
  if (error) throw error;
105
178
  return data.session ? mapSupabaseSession(data.session) : null;
106
179
  }
180
+ const tokenHash = param("token_hash") ?? param("token");
181
+ const type = param("type");
182
+ if (tokenHash && type) {
183
+ const { data, error } = await supabase.auth.verifyOtp({
184
+ token_hash: tokenHash,
185
+ type
186
+ });
187
+ if (error) throw error;
188
+ return data.session ? mapSupabaseSession(data.session) : null;
189
+ }
107
190
  const {
108
191
  data: { session }
109
192
  } = await supabase.auth.getSession();
@@ -115,7 +198,10 @@ function createSupabaseAuthAdapter(config) {
115
198
  options: {
116
199
  shouldCreateUser: true,
117
200
  emailRedirectTo: options?.redirectTo ?? (typeof window !== "undefined" ? `${window.location.origin}/` : void 0),
118
- data: options?.data
201
+ // `data` only lands on users the OTP actually creates, which is exactly
202
+ // who needs a password: the shell holds them on the set-password screen
203
+ // until they choose one, so they can sign in normally from then on.
204
+ data: { ...options?.data, [MUST_SET_PASSWORD_FLAG]: true }
119
205
  }
120
206
  });
121
207
  if (error) throw error;
@@ -396,6 +482,6 @@ function useAuth() {
396
482
  };
397
483
  }
398
484
 
399
- export { AuthProvider, createMockAuthAdapter, createSupabaseAuthAdapter, useAuth, useAuthStore };
485
+ export { AUTH_INTENT_PARAM, AuthProvider, MUST_SET_PASSWORD_FLAG, clearAuthUrlTokens, createMockAuthAdapter, createSupabaseAuthAdapter, decidePasswordSetup, readAuthUrlIntent, useAuth, useAuthStore, withAuthIntent };
400
486
  //# sourceMappingURL=index.js.map
401
487
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters/supabase.ts","../src/adapters/mock.ts","../src/store.ts","../src/context.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { createClient, type SupabaseClient } from '@supabase/supabase-js'\nimport type { AuthAdapter, AuthUser, AuthSession, AuthProvider } from '@fayz-ai/core'\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 }\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 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 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 const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo:\n options?.redirectTo ??\n config.resetPasswordUrl ??\n (typeof window !== 'undefined' ? `${window.location.origin}/auth/reset-password` : undefined),\n })\n if (error) throw error\n },\n\n async updatePassword(password) {\n const { data, error } = await supabase.auth.updateUser({ password })\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 code = new URL(target).searchParams.get('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 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: options?.data,\n },\n })\n if (error) throw error\n },\n }\n}\n","import type { AuthAdapter, AuthUser, AuthSession } from '@fayz-ai/core'\n\nexport interface MockUser {\n id: string\n email: string\n fullName: string\n role?: string\n}\n\nconst DEFAULT_MOCK_USER: MockUser = {\n id: 'mock-user-id',\n email: 'demo@fayz.dev',\n fullName: 'Demo User',\n role: 'admin',\n}\n\nconst STORAGE_KEY = 'fayz:mock-auth'\n\nfunction toAuthUser(mockUser: MockUser): AuthUser {\n return {\n id: mockUser.id,\n email: mockUser.email,\n fullName: mockUser.fullName,\n }\n}\n\nfunction createMockSession(user: AuthUser): AuthSession {\n return {\n accessToken: `mock-token-${user.id}`,\n refreshToken: `mock-refresh-${user.id}`,\n expiresAt: Math.floor(Date.now() / 1000) + 3600,\n user,\n }\n}\n\nfunction getStored(): { user: AuthUser; session: AuthSession } | null {\n try {\n if (typeof localStorage === 'undefined') return null\n const raw = localStorage.getItem(STORAGE_KEY)\n if (!raw) return null\n return JSON.parse(raw) as { user: AuthUser; session: AuthSession }\n } catch {\n return null\n }\n}\n\nfunction setStored(data: { user: AuthUser; session: AuthSession } | null): void {\n try {\n if (typeof localStorage === 'undefined') return\n if (data) {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(data))\n } else {\n localStorage.removeItem(STORAGE_KEY)\n }\n } catch {\n // SSR or storage unavailable — silently skip\n }\n}\n\nexport function createMockAuthAdapter(mockUser: MockUser = DEFAULT_MOCK_USER): AuthAdapter {\n const listeners = new Set<(user: AuthUser | null) => void>()\n\n function notifyListeners(user: AuthUser | null): void {\n for (const cb of listeners) cb(user)\n }\n\n return {\n async getSession() {\n return getStored()\n },\n\n onAuthStateChange(cb) {\n listeners.add(cb)\n return () => {\n listeners.delete(cb)\n }\n },\n\n async signIn(email, _password) {\n // Mock accepts any credentials, but the identity FOLLOWS the email —\n // storefront customers and saas users keep their own purchase/session\n // history instead of all becoming the configured demo user.\n const stored = getStored()\n const user: AuthUser =\n stored && stored.user.email === email\n ? stored.user\n : {\n id: email === mockUser.email ? mockUser.id : `mock-${email}`,\n email,\n fullName: email === mockUser.email ? mockUser.fullName : (email.split('@')[0] ?? 'Cliente'),\n }\n const session = createMockSession(user)\n setStored({ user, session })\n notifyListeners(user)\n return session\n },\n\n async signUp(email, _password, fullName) {\n // Mock signup creates a user from the provided details\n const user: AuthUser = {\n id: `mock-${email}`,\n email,\n fullName: fullName || (email.split('@')[0] ?? 'Cliente'),\n }\n const session = createMockSession(user)\n setStored({ user, session })\n notifyListeners(user)\n return session\n },\n\n async signOut() {\n setStored(null)\n notifyListeners(null)\n },\n\n async signInWithOAuth(_provider) {\n // Mock OAuth signs in as the configured user\n const user = toAuthUser(mockUser)\n const session = createMockSession(user)\n setStored({ user, session })\n notifyListeners(user)\n },\n\n async resetPassword(_email) {\n // No-op in mock\n },\n\n async updatePassword(_password) {\n const stored = getStored()\n return stored?.session\n },\n\n async handleCallback() {\n return getStored()?.session ?? null\n },\n\n async inviteUser(_email, _options) {\n // No admin path in mock — the org mock adapter still records the pending\n // invite so the Team UI shows it. Delivery is a no-op locally.\n },\n }\n}\n","import { create } from 'zustand'\nimport type { AuthUser, AuthSession } from '@fayz-ai/core'\n\nexport interface AuthState {\n user: AuthUser | null\n session: AuthSession | null\n isLoading: boolean\n isAuthenticated: boolean\n error: Error | null\n}\n\nexport interface AuthStore extends AuthState {\n setUser: (user: AuthUser | null) => void\n setSession: (session: AuthSession | null) => void\n setLoading: (isLoading: boolean) => void\n setError: (error: Error | null) => void\n reset: () => void\n}\n\nconst initialState: AuthState = {\n user: null,\n session: null,\n isLoading: true,\n isAuthenticated: false,\n error: null,\n}\n\nexport const useAuthStore = create<AuthStore>((set) => ({\n ...initialState,\n\n setUser: (user) =>\n set({ user, isAuthenticated: user !== null }),\n\n setSession: (session) =>\n set({\n session,\n user: session?.user ?? null,\n isAuthenticated: session !== null,\n }),\n\n setLoading: (isLoading) => set({ isLoading }),\n\n setError: (error) => set({ error }),\n\n reset: () => set(initialState),\n}))\n","import React, { createContext, useContext, useEffect, useRef } from 'react'\nimport type { AuthAdapter, AuthProvider as OAuthProvider, AuthSession } from '@fayz-ai/core'\nimport { useAuthStore } from './store'\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface AuthContextValue {\n signIn: (email: string, password: string) => Promise<AuthSession>\n signUp: (email: string, password: string, fullName: string) => Promise<AuthSession>\n signOut: () => Promise<void>\n signInWithOAuth: (provider: OAuthProvider) => Promise<void>\n resetPassword: (email: string, options?: { redirectTo?: string }) => Promise<void>\n updatePassword: (password: string) => Promise<AuthSession | void>\n handleCallback: (url?: string) => Promise<AuthSession | null>\n inviteUser: (email: string, options?: { redirectTo?: string; data?: Record<string, unknown> }) => Promise<void>\n}\n\nconst AuthContext = createContext<AuthContextValue | null>(null)\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport interface AuthProviderProps {\n adapter: AuthAdapter\n children: React.ReactNode\n}\n\nexport function AuthProvider({ adapter, children }: AuthProviderProps) {\n const { setSession, setLoading, setError, reset } = useAuthStore()\n\n // Keep a stable ref to the adapter so the effect only runs once on mount\n const adapterRef = useRef(adapter)\n adapterRef.current = adapter\n\n useEffect(() => {\n let unsubscribe: (() => void) | undefined\n\n async function init() {\n try {\n setLoading(true)\n const result = await adapterRef.current.getSession()\n setSession(result?.session ?? null)\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)))\n } finally {\n setLoading(false)\n }\n\n // Subscribe to future auth state changes\n unsubscribe = adapterRef.current.onAuthStateChange((user) => {\n if (!user) {\n reset()\n // Keep isLoading false after an explicit sign-out\n setLoading(false)\n return\n }\n // Positive change (adapter-driven sign-in — e.g. an external callback,\n // another tab, or a direct adapter.signUp bridge) — sync the session so\n // consumers reflect it even when the call didn't go through ctx.signIn.\n void adapterRef.current.getSession().then((result) => setSession(result?.session ?? null)).catch(() => {})\n })\n }\n\n void init()\n\n return () => {\n unsubscribe?.()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const value: AuthContextValue = {\n async signIn(email, password) {\n setError(null)\n try {\n const session = await adapterRef.current.signIn(email, password)\n setSession(session)\n return session\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async signUp(email, password, fullName) {\n setError(null)\n try {\n const session = await adapterRef.current.signUp(email, password, fullName)\n setSession(session)\n return session\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async signOut() {\n setError(null)\n try {\n await adapterRef.current.signOut()\n reset()\n setLoading(false)\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async signInWithOAuth(provider) {\n setError(null)\n try {\n await adapterRef.current.signInWithOAuth(provider)\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async resetPassword(email, options) {\n setError(null)\n try {\n await adapterRef.current.resetPassword(email, options)\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async updatePassword(password) {\n setError(null)\n try {\n const session = await adapterRef.current.updatePassword?.(password)\n if (session) setSession(session)\n return session\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async handleCallback(url) {\n setError(null)\n try {\n const session = await adapterRef.current.handleCallback?.(url) ?? null\n if (session) setSession(session)\n return session\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async inviteUser(email, options) {\n setError(null)\n try {\n await adapterRef.current.inviteUser?.(email, options)\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n }\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useAuth() {\n const ctx = useContext(AuthContext)\n if (!ctx) {\n throw new Error('useAuth must be used inside <AuthProvider>')\n }\n\n const { user, session, isLoading, isAuthenticated, error } = useAuthStore()\n\n return {\n user,\n session,\n isLoading,\n isAuthenticated,\n error,\n signIn: ctx.signIn,\n signUp: ctx.signUp,\n signOut: ctx.signOut,\n signInWithOAuth: ctx.signInWithOAuth,\n resetPassword: ctx.resetPassword,\n updatePassword: ctx.updatePassword,\n handleCallback: ctx.handleCallback,\n inviteUser: ctx.inviteUser,\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/url-intent.ts","../src/adapters/supabase.ts","../src/adapters/mock.ts","../src/store.ts","../src/context.tsx"],"names":[],"mappings":"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","file":"index.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\nexport 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\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 if (typeof window === 'undefined') return null\n return {\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.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/** 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\n// ---------------------------------------------------------------------------\n// Whether the user owes us a password before they reach the app. Pure, so the\n// decision is testable without a browser; the React wrapper lives in\n// @fayz-ai/plugin-auth (usePasswordSetupGate).\n// ---------------------------------------------------------------------------\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","import { createClient, type SupabaseClient } from '@supabase/supabase-js'\nimport type { AuthAdapter, AuthUser, AuthSession, AuthProvider } from '@fayz-ai/core'\nimport { withAuthIntent } from '../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 mustSetPassword: supabaseUser.user_metadata?.[MUST_SET_PASSWORD_FLAG] === true,\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 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 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","import type { AuthAdapter, AuthUser, AuthSession } from '@fayz-ai/core'\n\nexport interface MockUser {\n id: string\n email: string\n fullName: string\n role?: string\n}\n\nconst DEFAULT_MOCK_USER: MockUser = {\n id: 'mock-user-id',\n email: 'demo@fayz.dev',\n fullName: 'Demo User',\n role: 'admin',\n}\n\nconst STORAGE_KEY = 'fayz:mock-auth'\n\nfunction toAuthUser(mockUser: MockUser): AuthUser {\n return {\n id: mockUser.id,\n email: mockUser.email,\n fullName: mockUser.fullName,\n }\n}\n\nfunction createMockSession(user: AuthUser): AuthSession {\n return {\n accessToken: `mock-token-${user.id}`,\n refreshToken: `mock-refresh-${user.id}`,\n expiresAt: Math.floor(Date.now() / 1000) + 3600,\n user,\n }\n}\n\nfunction getStored(): { user: AuthUser; session: AuthSession } | null {\n try {\n if (typeof localStorage === 'undefined') return null\n const raw = localStorage.getItem(STORAGE_KEY)\n if (!raw) return null\n return JSON.parse(raw) as { user: AuthUser; session: AuthSession }\n } catch {\n return null\n }\n}\n\nfunction setStored(data: { user: AuthUser; session: AuthSession } | null): void {\n try {\n if (typeof localStorage === 'undefined') return\n if (data) {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(data))\n } else {\n localStorage.removeItem(STORAGE_KEY)\n }\n } catch {\n // SSR or storage unavailable — silently skip\n }\n}\n\nexport function createMockAuthAdapter(mockUser: MockUser = DEFAULT_MOCK_USER): AuthAdapter {\n const listeners = new Set<(user: AuthUser | null) => void>()\n\n function notifyListeners(user: AuthUser | null): void {\n for (const cb of listeners) cb(user)\n }\n\n return {\n async getSession() {\n return getStored()\n },\n\n onAuthStateChange(cb) {\n listeners.add(cb)\n return () => {\n listeners.delete(cb)\n }\n },\n\n async signIn(email, _password) {\n // Mock accepts any credentials, but the identity FOLLOWS the email —\n // storefront customers and saas users keep their own purchase/session\n // history instead of all becoming the configured demo user.\n const stored = getStored()\n const user: AuthUser =\n stored && stored.user.email === email\n ? stored.user\n : {\n id: email === mockUser.email ? mockUser.id : `mock-${email}`,\n email,\n fullName: email === mockUser.email ? mockUser.fullName : (email.split('@')[0] ?? 'Cliente'),\n }\n const session = createMockSession(user)\n setStored({ user, session })\n notifyListeners(user)\n return session\n },\n\n async signUp(email, _password, fullName) {\n // Mock signup creates a user from the provided details\n const user: AuthUser = {\n id: `mock-${email}`,\n email,\n fullName: fullName || (email.split('@')[0] ?? 'Cliente'),\n }\n const session = createMockSession(user)\n setStored({ user, session })\n notifyListeners(user)\n return session\n },\n\n async signOut() {\n setStored(null)\n notifyListeners(null)\n },\n\n async signInWithOAuth(_provider) {\n // Mock OAuth signs in as the configured user\n const user = toAuthUser(mockUser)\n const session = createMockSession(user)\n setStored({ user, session })\n notifyListeners(user)\n },\n\n async resetPassword(_email) {\n // No-op in mock\n },\n\n async updatePassword(_password) {\n const stored = getStored()\n return stored?.session\n },\n\n async handleCallback() {\n return getStored()?.session ?? null\n },\n\n async inviteUser(_email, _options) {\n // No admin path in mock — the org mock adapter still records the pending\n // invite so the Team UI shows it. Delivery is a no-op locally.\n },\n }\n}\n","import { create } from 'zustand'\nimport type { AuthUser, AuthSession } from '@fayz-ai/core'\n\nexport interface AuthState {\n user: AuthUser | null\n session: AuthSession | null\n isLoading: boolean\n isAuthenticated: boolean\n error: Error | null\n}\n\nexport interface AuthStore extends AuthState {\n setUser: (user: AuthUser | null) => void\n setSession: (session: AuthSession | null) => void\n setLoading: (isLoading: boolean) => void\n setError: (error: Error | null) => void\n reset: () => void\n}\n\nconst initialState: AuthState = {\n user: null,\n session: null,\n isLoading: true,\n isAuthenticated: false,\n error: null,\n}\n\nexport const useAuthStore = create<AuthStore>((set) => ({\n ...initialState,\n\n setUser: (user) =>\n set({ user, isAuthenticated: user !== null }),\n\n setSession: (session) =>\n set({\n session,\n user: session?.user ?? null,\n isAuthenticated: session !== null,\n }),\n\n setLoading: (isLoading) => set({ isLoading }),\n\n setError: (error) => set({ error }),\n\n reset: () => set(initialState),\n}))\n","import React, { createContext, useContext, useEffect, useRef } from 'react'\nimport type { AuthAdapter, AuthProvider as OAuthProvider, AuthSession } from '@fayz-ai/core'\nimport { useAuthStore } from './store'\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface AuthContextValue {\n signIn: (email: string, password: string) => Promise<AuthSession>\n signUp: (email: string, password: string, fullName: string) => Promise<AuthSession>\n signOut: () => Promise<void>\n signInWithOAuth: (provider: OAuthProvider) => Promise<void>\n resetPassword: (email: string, options?: { redirectTo?: string }) => Promise<void>\n updatePassword: (password: string) => Promise<AuthSession | void>\n handleCallback: (url?: string) => Promise<AuthSession | null>\n inviteUser: (email: string, options?: { redirectTo?: string; data?: Record<string, unknown> }) => Promise<void>\n}\n\nconst AuthContext = createContext<AuthContextValue | null>(null)\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport interface AuthProviderProps {\n adapter: AuthAdapter\n children: React.ReactNode\n}\n\nexport function AuthProvider({ adapter, children }: AuthProviderProps) {\n const { setSession, setLoading, setError, reset } = useAuthStore()\n\n // Keep a stable ref to the adapter so the effect only runs once on mount\n const adapterRef = useRef(adapter)\n adapterRef.current = adapter\n\n useEffect(() => {\n let unsubscribe: (() => void) | undefined\n\n async function init() {\n try {\n setLoading(true)\n const result = await adapterRef.current.getSession()\n setSession(result?.session ?? null)\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)))\n } finally {\n setLoading(false)\n }\n\n // Subscribe to future auth state changes\n unsubscribe = adapterRef.current.onAuthStateChange((user) => {\n if (!user) {\n reset()\n // Keep isLoading false after an explicit sign-out\n setLoading(false)\n return\n }\n // Positive change (adapter-driven sign-in — e.g. an external callback,\n // another tab, or a direct adapter.signUp bridge) — sync the session so\n // consumers reflect it even when the call didn't go through ctx.signIn.\n void adapterRef.current.getSession().then((result) => setSession(result?.session ?? null)).catch(() => {})\n })\n }\n\n void init()\n\n return () => {\n unsubscribe?.()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const value: AuthContextValue = {\n async signIn(email, password) {\n setError(null)\n try {\n const session = await adapterRef.current.signIn(email, password)\n setSession(session)\n return session\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async signUp(email, password, fullName) {\n setError(null)\n try {\n const session = await adapterRef.current.signUp(email, password, fullName)\n setSession(session)\n return session\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async signOut() {\n setError(null)\n try {\n await adapterRef.current.signOut()\n reset()\n setLoading(false)\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async signInWithOAuth(provider) {\n setError(null)\n try {\n await adapterRef.current.signInWithOAuth(provider)\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async resetPassword(email, options) {\n setError(null)\n try {\n await adapterRef.current.resetPassword(email, options)\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async updatePassword(password) {\n setError(null)\n try {\n const session = await adapterRef.current.updatePassword?.(password)\n if (session) setSession(session)\n return session\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async handleCallback(url) {\n setError(null)\n try {\n const session = await adapterRef.current.handleCallback?.(url) ?? null\n if (session) setSession(session)\n return session\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n\n async inviteUser(email, options) {\n setError(null)\n try {\n await adapterRef.current.inviteUser?.(email, options)\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n setError(error)\n throw error\n }\n },\n }\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useAuth() {\n const ctx = useContext(AuthContext)\n if (!ctx) {\n throw new Error('useAuth must be used inside <AuthProvider>')\n }\n\n const { user, session, isLoading, isAuthenticated, error } = useAuthStore()\n\n return {\n user,\n session,\n isLoading,\n isAuthenticated,\n error,\n signIn: ctx.signIn,\n signUp: ctx.signUp,\n signOut: ctx.signOut,\n signInWithOAuth: ctx.signInWithOAuth,\n resetPassword: ctx.resetPassword,\n updatePassword: ctx.updatePassword,\n handleCallback: ctx.handleCallback,\n inviteUser: ctx.inviteUser,\n }\n}\n"]}
@@ -0,0 +1,43 @@
1
+ export type AuthUrlIntent =
2
+ /** A password-recovery link — the user must choose a new password now. */
3
+ {
4
+ kind: 'recovery';
5
+ }
6
+ /** A one-time link that was already used or has expired. */
7
+ | {
8
+ kind: 'expired';
9
+ message?: string;
10
+ };
11
+ /** Query param apps can put on their own redirect URLs to force the
12
+ * set-password screen (e.g. when they override `resetPasswordUrl`). */
13
+ export declare const AUTH_INTENT_PARAM = "fayz_auth";
14
+ interface UrlSnapshot {
15
+ pathname: string;
16
+ search: string;
17
+ hash: string;
18
+ }
19
+ export declare function readAuthUrlIntent(url?: UrlSnapshot | null): AuthUrlIntent | null;
20
+ /** Strip auth leftovers from the address bar once the flow is finished, so a
21
+ * reload does not put the user back on the set-password screen (and so the
22
+ * hash router gets a clean slate). */
23
+ export declare function clearAuthUrlTokens(): void;
24
+ /** Add the intent marker to a redirect URL. Absolute or relative; existing
25
+ * params are preserved. */
26
+ export declare function withAuthIntent(url: string, intent: 'recovery'): string;
27
+ export type PasswordSetupReason = 'recovery' | 'invite';
28
+ export interface PasswordSetupInput {
29
+ intent: AuthUrlIntent | null;
30
+ /** Session still resolving — the link's tokens are exchanged asynchronously. */
31
+ isLoading: boolean;
32
+ isAuthenticated: boolean;
33
+ /** AuthUser.mustSetPassword — an invitee who never chose one. */
34
+ mustSetPassword?: boolean;
35
+ }
36
+ export interface PasswordSetupDecision {
37
+ reason: PasswordSetupReason | null;
38
+ expired: boolean;
39
+ checking: boolean;
40
+ }
41
+ export declare function decidePasswordSetup(input: PasswordSetupInput): PasswordSetupDecision;
42
+ export {};
43
+ //# sourceMappingURL=url-intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-intent.d.ts","sourceRoot":"","sources":["../src/url-intent.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,aAAa;AACvB,0EAA0E;AACxE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE;AACtB,4DAA4D;GAC1D;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzC;wEACwE;AACxE,eAAO,MAAM,iBAAiB,cAAc,CAAA;AAM5C,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb;AAsBD,wBAAgB,iBAAiB,CAAC,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,aAAa,GAAG,IAAI,CA4BhF;AAED;;uCAEuC;AACvC,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC;AAED;4BAC4B;AAC5B,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAKtE;AAQD,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,QAAQ,CAAA;AAEvD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IAC5B,gFAAgF;IAChF,SAAS,EAAE,OAAO,CAAA;IAClB,eAAe,EAAE,OAAO,CAAA;IACxB,iEAAiE;IACjE,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAA;IAClC,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAID,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,kBAAkB,GAAG,qBAAqB,CAgBpF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=url-intent.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-intent.test.d.ts","sourceRoot":"","sources":["../src/url-intent.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "fayz": {
4
4
  "status": "beta"
5
5
  },
6
- "version": "0.9.0-next.0",
6
+ "version": "0.9.0",
7
7
  "description": "Fayz SDK auth — pluggable authentication adapters with React hooks",
8
8
  "type": "module",
9
9
  "sideEffects": false,
@@ -23,11 +23,11 @@
23
23
  ],
24
24
  "peerDependencies": {
25
25
  "react": "^18.0.0 || ^19.0.0",
26
- "zustand": "^4.5.0",
26
+ "zustand": "^4.5.0 || ^5.0.0",
27
27
  "@supabase/supabase-js": "^2.45.0"
28
28
  },
29
29
  "dependencies": {
30
- "@fayz-ai/core": "^0.9.0-next.0"
30
+ "@fayz-ai/core": "^0.9.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/react": "^18.3.0",
@@ -35,7 +35,8 @@
35
35
  "tsup": "^8.2.0",
36
36
  "typescript": "^5.5.0",
37
37
  "zustand": "^4.5.0",
38
- "@supabase/supabase-js": "^2.45.0"
38
+ "@supabase/supabase-js": "^2.45.0",
39
+ "vitest": "^4.1.5"
39
40
  },
40
41
  "license": "MIT",
41
42
  "publishConfig": {
@@ -45,6 +46,7 @@
45
46
  "build": "tsup && tsc --emitDeclarationOnly --declaration --declarationMap --noEmit false",
46
47
  "dev": "tsup --watch",
47
48
  "typecheck": "tsc --noEmit",
49
+ "test": "vitest run",
48
50
  "clean": "rm -rf dist"
49
51
  }
50
52
  }
@@ -1,5 +1,12 @@
1
1
  import { createClient, type SupabaseClient } from '@supabase/supabase-js'
2
2
  import type { AuthAdapter, AuthUser, AuthSession, AuthProvider } from '@fayz-ai/core'
3
+ import { withAuthIntent } from '../url-intent'
4
+
5
+ /** Metadata flag stamped on invited users (created by a magic link, so they have
6
+ * no password yet) and cleared the moment they choose one. Metadata, not a URL
7
+ * param: it survives the link being opened later, in another browser, or twice —
8
+ * and it needs no new entry in Supabase's redirect allow-list. */
9
+ export const MUST_SET_PASSWORD_FLAG = 'fayz_must_set_password'
3
10
 
4
11
  export interface SupabaseAuthConfig {
5
12
  supabaseUrl: string
@@ -26,6 +33,7 @@ function mapSupabaseUser(supabaseUser: {
26
33
  (supabaseUser.user_metadata?.['name'] as string | undefined) ??
27
34
  '',
28
35
  avatarUrl: supabaseUser.user_metadata?.['avatar_url'] as string | undefined,
36
+ mustSetPassword: supabaseUser.user_metadata?.[MUST_SET_PASSWORD_FLAG] === true,
29
37
  }
30
38
  }
31
39
 
@@ -114,17 +122,31 @@ export function createSupabaseAuthAdapter(config: SupabaseAuthConfig): AuthAdapt
114
122
  },
115
123
 
116
124
  async resetPassword(email, options) {
125
+ // Root, not `/auth/reset-password`: the shell recognises a recovery link by
126
+ // the link's own `type=recovery` param, so the flow needs no route of its
127
+ // own — and the app root is the one URL every deployment already serves and
128
+ // every Supabase project already allow-lists (a URL outside the allow-list
129
+ // is silently swapped for the project's Site URL, i.e. another app).
130
+ // A host that DOES point recovery somewhere else gets the explicit intent
131
+ // marker, since we cannot assume its landing page carries the params.
132
+ const explicit = options?.redirectTo ?? config.resetPasswordUrl
117
133
  const { error } = await supabase.auth.resetPasswordForEmail(email, {
118
- redirectTo:
119
- options?.redirectTo ??
120
- config.resetPasswordUrl ??
121
- (typeof window !== 'undefined' ? `${window.location.origin}/auth/reset-password` : undefined),
134
+ redirectTo: explicit
135
+ ? withAuthIntent(explicit, 'recovery')
136
+ : typeof window !== 'undefined'
137
+ ? `${window.location.origin}/`
138
+ : undefined,
122
139
  })
123
140
  if (error) throw error
124
141
  },
125
142
 
126
143
  async updatePassword(password) {
127
- const { data, error } = await supabase.auth.updateUser({ password })
144
+ // Clearing the flag in the same call as the password: one round-trip, and
145
+ // no window where the password is set but the screen still demands one.
146
+ const { data, error } = await supabase.auth.updateUser({
147
+ password,
148
+ data: { [MUST_SET_PASSWORD_FLAG]: false },
149
+ })
128
150
  if (error) throw error
129
151
  const {
130
152
  data: { session },
@@ -141,12 +163,32 @@ export function createSupabaseAuthAdapter(config: SupabaseAuthConfig): AuthAdapt
141
163
 
142
164
  async handleCallback(url) {
143
165
  const target = url ?? (typeof window !== 'undefined' ? window.location.href : '')
144
- const code = new URL(target).searchParams.get('code')
166
+ const parsed = new URL(target)
167
+ // The fragment carries the params on the implicit flow; the query on PKCE
168
+ // and on `{{ .TokenHash }}`-style e-mail templates.
169
+ const fragment = new URLSearchParams(parsed.hash.replace(/^#/, ''))
170
+ const param = (key: string) => parsed.searchParams.get(key) ?? fragment.get(key)
171
+
172
+ const code = param('code')
145
173
  if (code) {
146
174
  const { data, error } = await supabase.auth.exchangeCodeForSession(code)
147
175
  if (error) throw error
148
176
  return data.session ? mapSupabaseSession(data.session) : null
149
177
  }
178
+
179
+ // Templates that link straight at the app with a token instead of routing
180
+ // through GoTrue's /verify never produce a session on their own.
181
+ const tokenHash = param('token_hash') ?? param('token')
182
+ const type = param('type')
183
+ if (tokenHash && type) {
184
+ const { data, error } = await supabase.auth.verifyOtp({
185
+ token_hash: tokenHash,
186
+ type: type as 'recovery' | 'invite' | 'magiclink' | 'signup' | 'email',
187
+ })
188
+ if (error) throw error
189
+ return data.session ? mapSupabaseSession(data.session) : null
190
+ }
191
+
150
192
  const {
151
193
  data: { session },
152
194
  } = await supabase.auth.getSession()
@@ -165,7 +207,10 @@ export function createSupabaseAuthAdapter(config: SupabaseAuthConfig): AuthAdapt
165
207
  emailRedirectTo:
166
208
  options?.redirectTo ??
167
209
  (typeof window !== 'undefined' ? `${window.location.origin}/` : undefined),
168
- data: options?.data,
210
+ // `data` only lands on users the OTP actually creates, which is exactly
211
+ // who needs a password: the shell holds them on the set-password screen
212
+ // until they choose one, so they can sign in normally from then on.
213
+ data: { ...options?.data, [MUST_SET_PASSWORD_FLAG]: true },
169
214
  },
170
215
  })
171
216
  if (error) throw error
package/src/index.ts CHANGED
@@ -1,7 +1,22 @@
1
1
  // Adapters
2
- export { createSupabaseAuthAdapter } from './adapters/supabase'
2
+ export { createSupabaseAuthAdapter, MUST_SET_PASSWORD_FLAG } from './adapters/supabase'
3
3
  export type { SupabaseAuthConfig } from './adapters/supabase'
4
4
 
5
+ // Auth link intent (recovery / expired one-time link) + set-password decision
6
+ export {
7
+ readAuthUrlIntent,
8
+ clearAuthUrlTokens,
9
+ withAuthIntent,
10
+ decidePasswordSetup,
11
+ AUTH_INTENT_PARAM,
12
+ } from './url-intent'
13
+ export type {
14
+ AuthUrlIntent,
15
+ PasswordSetupDecision,
16
+ PasswordSetupInput,
17
+ PasswordSetupReason,
18
+ } from './url-intent'
19
+
5
20
  export { createMockAuthAdapter } from './adapters/mock'
6
21
  export type { MockUser } from './adapters/mock'
7
22
 
@@ -0,0 +1,91 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { readAuthUrlIntent, withAuthIntent, decidePasswordSetup } from './url-intent'
3
+
4
+ function url(pathname: string, search = '', hash = '') {
5
+ return { pathname, search, hash }
6
+ }
7
+
8
+ describe('readAuthUrlIntent', () => {
9
+ it('recognises a recovery link by its implicit-flow fragment', () => {
10
+ expect(
11
+ readAuthUrlIntent(url('/', '', '#access_token=abc&refresh_token=def&type=recovery')),
12
+ ).toEqual({ kind: 'recovery' })
13
+ })
14
+
15
+ it('recognises the explicit marker for hosts that override the redirect', () => {
16
+ expect(readAuthUrlIntent(url('/', '?fayz_auth=recovery'))).toEqual({ kind: 'recovery' })
17
+ })
18
+
19
+ it('still honours the legacy /auth/reset-password path', () => {
20
+ expect(readAuthUrlIntent(url('/auth/reset-password'))).toEqual({ kind: 'recovery' })
21
+ })
22
+
23
+ it('flags a used or expired one-time link', () => {
24
+ const intent = readAuthUrlIntent(
25
+ url('/', '', '#error=access_denied&error_code=otp_expired&error_description=Email+link+is+invalid+or+has+expired'),
26
+ )
27
+ expect(intent?.kind).toBe('expired')
28
+ expect(intent && 'message' in intent && intent.message).toBe('Email link is invalid or has expired')
29
+ })
30
+
31
+ it('leaves a plain sign-in, a magic link and an OAuth denial alone', () => {
32
+ expect(readAuthUrlIntent(url('/', '', '#/dashboard'))).toBeNull()
33
+ expect(readAuthUrlIntent(url('/', '', '#access_token=abc&type=magiclink'))).toBeNull()
34
+ expect(readAuthUrlIntent(url('/', '', '#error=access_denied&error_code=user_banned'))).toBeNull()
35
+ })
36
+ })
37
+
38
+ describe('decidePasswordSetup', () => {
39
+ const signedIn = { isLoading: false, isAuthenticated: true }
40
+ const signedOut = { isLoading: false, isAuthenticated: false }
41
+
42
+ it('asks for a new password on a recovery link', () => {
43
+ expect(decidePasswordSetup({ intent: { kind: 'recovery' }, ...signedIn })).toEqual({
44
+ reason: 'recovery',
45
+ expired: false,
46
+ checking: false,
47
+ })
48
+ })
49
+
50
+ it('waits for the link session instead of flashing the login form', () => {
51
+ expect(
52
+ decidePasswordSetup({ intent: { kind: 'recovery' }, isLoading: true, isAuthenticated: false }),
53
+ ).toEqual({ reason: 'recovery', expired: false, checking: true })
54
+ })
55
+
56
+ it('treats a recovery link that produced no session as expired', () => {
57
+ expect(decidePasswordSetup({ intent: { kind: 'recovery' }, ...signedOut })).toEqual({
58
+ reason: 'recovery',
59
+ expired: true,
60
+ checking: false,
61
+ })
62
+ })
63
+
64
+ it('holds an invitee on the screen until they choose a password', () => {
65
+ expect(decidePasswordSetup({ intent: null, ...signedIn, mustSetPassword: true })).toEqual({
66
+ reason: 'invite',
67
+ expired: false,
68
+ checking: false,
69
+ })
70
+ })
71
+
72
+ it('stays out of the way for everyone else', () => {
73
+ expect(decidePasswordSetup({ intent: null, ...signedIn }).reason).toBeNull()
74
+ expect(decidePasswordSetup({ intent: null, ...signedOut }).reason).toBeNull()
75
+ // Never gate the login form itself: no session, nothing to update.
76
+ expect(decidePasswordSetup({ intent: null, ...signedOut, mustSetPassword: true }).reason).toBeNull()
77
+ })
78
+ })
79
+
80
+ describe('withAuthIntent', () => {
81
+ it('keeps existing params and stays absolute', () => {
82
+ expect(withAuthIntent('https://app.example.com/?ref=x', 'recovery')).toBe(
83
+ 'https://app.example.com/?ref=x&fayz_auth=recovery',
84
+ )
85
+ })
86
+
87
+ it('is idempotent', () => {
88
+ const once = withAuthIntent('https://app.example.com/', 'recovery')
89
+ expect(withAuthIntent(once, 'recovery')).toBe(once)
90
+ })
91
+ })
@@ -0,0 +1,151 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Auth link intent — what the URL the user just arrived on is asking for.
3
+ //
4
+ // A Supabase recovery / magic link lands on the app with the tokens in the URL
5
+ // FRAGMENT (implicit flow is the supabase-js default), e.g.
6
+ //
7
+ // https://app.example.com/#access_token=…&refresh_token=…&type=recovery
8
+ //
9
+ // GoTrue then consumes that fragment and wipes it (`window.location.hash = ''`)
10
+ // as soon as the client initialises — so by the time React renders, the reason
11
+ // the user is here is gone. Hence the module-load snapshot: this file is
12
+ // imported long before any createClient() call, so it always sees the original
13
+ // URL.
14
+ //
15
+ // Errors arrive the same way, without a `type`:
16
+ //
17
+ // https://app.example.com/#error=access_denied&error_code=otp_expired&…
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export type AuthUrlIntent =
21
+ /** A password-recovery link — the user must choose a new password now. */
22
+ | { kind: 'recovery' }
23
+ /** A one-time link that was already used or has expired. */
24
+ | { kind: 'expired'; message?: string }
25
+
26
+ /** Query param apps can put on their own redirect URLs to force the
27
+ * set-password screen (e.g. when they override `resetPasswordUrl`). */
28
+ export const AUTH_INTENT_PARAM = 'fayz_auth'
29
+
30
+ /** Path segment the SDK historically used as the reset-password redirect.
31
+ * Still honoured so apps that configured it keep working. */
32
+ const RESET_PATH = '/auth/reset-password'
33
+
34
+ interface UrlSnapshot {
35
+ pathname: string
36
+ search: string
37
+ hash: string
38
+ }
39
+
40
+ function snapshot(): UrlSnapshot | null {
41
+ if (typeof window === 'undefined') return null
42
+ return {
43
+ pathname: window.location.pathname,
44
+ search: window.location.search,
45
+ hash: window.location.hash,
46
+ }
47
+ }
48
+
49
+ /** Taken at import time — before any Supabase client can strip the fragment. */
50
+ const initialUrl: UrlSnapshot | null = snapshot()
51
+
52
+ /** The fragment can be either auth params (`access_token=…&type=recovery`) or a
53
+ * hash-router path (`#/dashboard`). Only the former parses into pairs. */
54
+ function hashParams(hash: string): URLSearchParams {
55
+ const raw = hash.replace(/^#/, '')
56
+ if (!raw || !raw.includes('=')) return new URLSearchParams()
57
+ return new URLSearchParams(raw)
58
+ }
59
+
60
+ export function readAuthUrlIntent(url?: UrlSnapshot | null): AuthUrlIntent | null {
61
+ const loc = url ?? initialUrl
62
+ if (!loc) return null
63
+
64
+ const query = new URLSearchParams(loc.search)
65
+ const hash = hashParams(loc.hash)
66
+ const param = (key: string) => hash.get(key) ?? query.get(key)
67
+
68
+ const errorCode = param('error_code') ?? param('error')
69
+ const errorDescription = param('error_description')?.replace(/\+/g, ' ')
70
+ // A dead one-time link (`error_code=otp_expired`). Deliberately narrow: an
71
+ // OAuth `access_denied` is a different failure and must not land here. GoTrue
72
+ // does not echo `type` on error redirects, so recovery and invite links are
73
+ // indistinguishable at this point — the screen offers a fresh link either way.
74
+ if (errorCode && /otp|expired|link/i.test(`${errorCode} ${errorDescription ?? ''}`)) {
75
+ return { kind: 'expired', message: errorDescription ?? undefined }
76
+ }
77
+
78
+ const marker = query.get(AUTH_INTENT_PARAM)
79
+ const type = param('type')
80
+ if (type === 'recovery' || marker === 'recovery') return { kind: 'recovery' }
81
+
82
+ // `/auth/reset-password` with no tokens at all: the user opened the reset URL
83
+ // directly, or the tokens were already consumed by a previous page load.
84
+ const path = loc.pathname.replace(/\/+$/, '')
85
+ if (path.endsWith(RESET_PATH)) return { kind: 'recovery' }
86
+
87
+ return null
88
+ }
89
+
90
+ /** Strip auth leftovers from the address bar once the flow is finished, so a
91
+ * reload does not put the user back on the set-password screen (and so the
92
+ * hash router gets a clean slate). */
93
+ export function clearAuthUrlTokens(): void {
94
+ if (typeof window === 'undefined') return
95
+ const url = new URL(window.location.href)
96
+ url.searchParams.delete(AUTH_INTENT_PARAM)
97
+ if (url.hash.includes('=')) url.hash = ''
98
+ window.history.replaceState(window.history.state, '', url.toString())
99
+ }
100
+
101
+ /** Add the intent marker to a redirect URL. Absolute or relative; existing
102
+ * params are preserved. */
103
+ export function withAuthIntent(url: string, intent: 'recovery'): string {
104
+ const base = typeof window === 'undefined' ? 'http://localhost' : window.location.origin
105
+ const parsed = new URL(url, base)
106
+ parsed.searchParams.set(AUTH_INTENT_PARAM, intent)
107
+ return /^https?:\/\//i.test(url) ? parsed.toString() : `${parsed.pathname}${parsed.search}${parsed.hash}`
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Whether the user owes us a password before they reach the app. Pure, so the
112
+ // decision is testable without a browser; the React wrapper lives in
113
+ // @fayz-ai/plugin-auth (usePasswordSetupGate).
114
+ // ---------------------------------------------------------------------------
115
+
116
+ export type PasswordSetupReason = 'recovery' | 'invite'
117
+
118
+ export interface PasswordSetupInput {
119
+ intent: AuthUrlIntent | null
120
+ /** Session still resolving — the link's tokens are exchanged asynchronously. */
121
+ isLoading: boolean
122
+ isAuthenticated: boolean
123
+ /** AuthUser.mustSetPassword — an invitee who never chose one. */
124
+ mustSetPassword?: boolean
125
+ }
126
+
127
+ export interface PasswordSetupDecision {
128
+ reason: PasswordSetupReason | null
129
+ expired: boolean
130
+ checking: boolean
131
+ }
132
+
133
+ const NOTHING: PasswordSetupDecision = { reason: null, expired: false, checking: false }
134
+
135
+ export function decidePasswordSetup(input: PasswordSetupInput): PasswordSetupDecision {
136
+ const { intent, isLoading, isAuthenticated, mustSetPassword } = input
137
+
138
+ if (intent?.kind === 'expired') return { reason: 'recovery', expired: true, checking: false }
139
+
140
+ if (intent?.kind === 'recovery') {
141
+ if (isLoading) return { reason: 'recovery', expired: false, checking: true }
142
+ // A recovery link with no session behind it was already used or has expired.
143
+ return { reason: 'recovery', expired: !isAuthenticated, checking: false }
144
+ }
145
+
146
+ if (!isLoading && isAuthenticated && mustSetPassword) {
147
+ return { reason: 'invite', expired: false, checking: false }
148
+ }
149
+
150
+ return NOTHING
151
+ }