@edcalderon/auth 1.0.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/CHANGELOG.md +13 -0
- package/README.md +514 -0
- package/dist/AuthProvider.d.ts +17 -0
- package/dist/AuthProvider.js +83 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/providers/FirebaseClient.d.ts +22 -0
- package/dist/providers/FirebaseClient.js +57 -0
- package/dist/providers/HybridClient.d.ts +29 -0
- package/dist/providers/HybridClient.js +108 -0
- package/dist/providers/SupabaseClient.d.ts +13 -0
- package/dist/providers/SupabaseClient.js +64 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.js +2 -0
- package/package.json +54 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.0] - 2026-03-01
|
|
4
|
+
|
|
5
|
+
### Initial Release
|
|
6
|
+
|
|
7
|
+
- β¨ Provider-agnostic `AuthClient` interface
|
|
8
|
+
- π Built-in Supabase adapter (`SupabaseClient`)
|
|
9
|
+
- π Built-in Firebase adapter (`FirebaseClient`)
|
|
10
|
+
- π Hybrid adapter for FirebaseβSupabase federated flows (`HybridClient`)
|
|
11
|
+
- βοΈ React `AuthProvider` and `useAuth` hook
|
|
12
|
+
- π‘οΈ Unified `User` type across all providers
|
|
13
|
+
- π¦ Published as `@edcalderon/auth` on NPM
|
package/README.md
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
# @edcalderon/auth
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@edcalderon/auth)
|
|
4
|
+
[](https://www.npmjs.com/package/@edcalderon/auth)
|
|
5
|
+
[](https://github.com/edcalderon/my-second-brain/tree/main/packages/auth)
|
|
6
|
+
|
|
7
|
+
A universal, **provider-agnostic** authentication orchestration package for React applications. Swap between Supabase, Firebase, Directus, Google OAuth, or any custom provider without changing a single line of component code.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## π Latest Changes (v1.0.0)
|
|
12
|
+
|
|
13
|
+
### Initial Release
|
|
14
|
+
|
|
15
|
+
- β¨ Provider-agnostic `AuthClient` interface
|
|
16
|
+
- π Built-in adapters: **Supabase**, **Firebase**, **Hybrid** (FirebaseβSupabase)
|
|
17
|
+
- βοΈ React `AuthProvider` context and `useAuth` hook
|
|
18
|
+
- π‘οΈ Unified `User` type across all providers
|
|
19
|
+
- π Session token access via `getSessionToken()`
|
|
20
|
+
|
|
21
|
+
For full version history, see [CHANGELOG.md](./CHANGELOG.md) and [GitHub releases](https://github.com/edcalderon/my-second-brain/releases)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ποΈ Architecture
|
|
26
|
+
|
|
27
|
+
The package follows a **Single Source of Truth** model with a **Federated OAuth Strategy**:
|
|
28
|
+
|
|
29
|
+
- **Principal Database (Source of Truth)**: Supabase anchors user identities, metadata, roles, and RLS policies in PostgreSQL (`auth.users`, `auth.identities`).
|
|
30
|
+
- **OAuth / Identity Providers**: External services (Firebase, Directus, native Google OAuth, Auth0, etc.) handle frontend login bridges or federated SSO flows.
|
|
31
|
+
- **The Orchestrator (`@edcalderon/auth`)**: A thin bridge layer that exposes generic interfaces (`User`, `AuthClient`). Applications consume a unified context without coupling to any specific vendor.
|
|
32
|
+
|
|
33
|
+
```mermaid
|
|
34
|
+
graph TD
|
|
35
|
+
UI[Frontend Applications] -->|useAuth| EdAuth["@edcalderon/auth"]
|
|
36
|
+
EdAuth -->|Direct Session| Supabase(Supabase)
|
|
37
|
+
EdAuth -->|Federated Bridge| Firebase(Firebase OAuth)
|
|
38
|
+
EdAuth -->|Custom Adapter| Directus(Directus SSO)
|
|
39
|
+
EdAuth -->|Custom Adapter| Custom(Auth0 / Custom)
|
|
40
|
+
|
|
41
|
+
Firebase -->|Sync Session| Supabase
|
|
42
|
+
Directus -->|Sync Session| Supabase
|
|
43
|
+
|
|
44
|
+
Supabase -->|Roles & Scopes| DB[(PostgreSQL)]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- π― **Provider-Agnostic** β one interface, any backend
|
|
52
|
+
- βοΈ **React-First** β `AuthProvider` + `useAuth` hook with full TypeScript types
|
|
53
|
+
- π **Extensible Adapter System** β implement `AuthClient` to add any provider
|
|
54
|
+
- π **Hybrid Flows** β Firebase popup β Supabase session bridging out of the box
|
|
55
|
+
- π‘οΈ **Unified User Model** β `User` type normalizes identities across providers
|
|
56
|
+
- π **Session Token Access** β `getSessionToken()` for API calls regardless of provider
|
|
57
|
+
- π¦ **Tree-Shakeable** β import only the adapters you need
|
|
58
|
+
- ποΈ **Zero Lock-In** β swap providers by changing one line of dependency injection
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
### From NPM (public)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install @edcalderon/auth
|
|
68
|
+
# or
|
|
69
|
+
pnpm add @edcalderon/auth
|
|
70
|
+
# or
|
|
71
|
+
yarn add @edcalderon/auth
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### From Monorepo (internal workspace)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pnpm --filter <your-app> add @edcalderon/auth@workspace:*
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Peer Dependencies
|
|
81
|
+
|
|
82
|
+
Install the peer dependencies for your chosen provider(s):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# For Supabase
|
|
86
|
+
pnpm add @supabase/supabase-js
|
|
87
|
+
|
|
88
|
+
# For Firebase
|
|
89
|
+
pnpm add firebase
|
|
90
|
+
|
|
91
|
+
# For Hybrid (Firebase + Supabase)
|
|
92
|
+
pnpm add @supabase/supabase-js firebase
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> **Note:** `react` and `react-dom` (v18+ or v19+) are required peer dependencies.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Quick Start
|
|
100
|
+
|
|
101
|
+
### 1. Choose Your Provider
|
|
102
|
+
|
|
103
|
+
| Provider | Class | Peer Dependency | Use Case |
|
|
104
|
+
|----------|-------|-----------------|----------|
|
|
105
|
+
| Supabase | `SupabaseClient` | `@supabase/supabase-js` | Direct Supabase Auth |
|
|
106
|
+
| Firebase | `FirebaseClient` | `firebase` | Firebase-only applications |
|
|
107
|
+
| Hybrid | `HybridClient` | Both | Firebase popup β Supabase session |
|
|
108
|
+
| Custom | Implement `AuthClient` | Your choice | Directus, Auth0, Keycloak, etc. |
|
|
109
|
+
|
|
110
|
+
### 2. Create the Provider Wrapper
|
|
111
|
+
|
|
112
|
+
#### Supabase (Direct)
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
// components/auth/AuthProvider.tsx
|
|
116
|
+
"use client";
|
|
117
|
+
|
|
118
|
+
import { AuthProvider as UniversalAuthProvider, SupabaseClient, useAuth as useUniversalAuth } from "@edcalderon/auth";
|
|
119
|
+
import { supabase } from "@/lib/supabase";
|
|
120
|
+
import { useMemo, type ReactNode } from "react";
|
|
121
|
+
|
|
122
|
+
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
123
|
+
const client = useMemo(() => new SupabaseClient(supabase), []);
|
|
124
|
+
return <UniversalAuthProvider client={client}>{children}</UniversalAuthProvider>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const useAuth = useUniversalAuth;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Hybrid (Firebase β Supabase)
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
"use client";
|
|
134
|
+
|
|
135
|
+
import { AuthProvider as UniversalAuthProvider, HybridClient, useAuth as useUniversalAuth } from "@edcalderon/auth";
|
|
136
|
+
import { supabase } from "@/lib/supabase";
|
|
137
|
+
import { auth, googleProvider, signInWithPopup, signOut, GoogleAuthProvider } from "@/lib/firebase";
|
|
138
|
+
import { useMemo, type ReactNode } from "react";
|
|
139
|
+
|
|
140
|
+
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
141
|
+
const client = useMemo(() => new HybridClient({
|
|
142
|
+
supabase,
|
|
143
|
+
firebaseAuth: auth,
|
|
144
|
+
firebaseMethods: {
|
|
145
|
+
signInWithPopup,
|
|
146
|
+
signOut,
|
|
147
|
+
credentialFromResult: GoogleAuthProvider.credentialFromResult,
|
|
148
|
+
},
|
|
149
|
+
googleProvider,
|
|
150
|
+
}), []);
|
|
151
|
+
|
|
152
|
+
return <UniversalAuthProvider client={client}>{children}</UniversalAuthProvider>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const useAuth = useUniversalAuth;
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 3. Use in Components
|
|
159
|
+
|
|
160
|
+
Every component consumes **identical signatures** regardless of which provider is active:
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import { useAuth } from "@/components/auth/AuthProvider";
|
|
164
|
+
|
|
165
|
+
export default function Dashboard() {
|
|
166
|
+
const { user, loading, error, signInWithGoogle, signOutUser } = useAuth();
|
|
167
|
+
|
|
168
|
+
if (loading) return <Spinner />;
|
|
169
|
+
if (error) return <p>Error: {error}</p>;
|
|
170
|
+
if (!user) return <button onClick={() => signInWithGoogle()}>Sign In with Google</button>;
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div>
|
|
174
|
+
<p>Welcome, {user.email} (via {user.provider})</p>
|
|
175
|
+
<button onClick={signOutUser}>Sign Out</button>
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## π Extensibility β Custom Adapters
|
|
184
|
+
|
|
185
|
+
The core strength of `@edcalderon/auth` is that **any authentication provider** can be integrated by implementing the `AuthClient` interface. No changes to your React components are required.
|
|
186
|
+
|
|
187
|
+
### The `AuthClient` Interface
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
export interface AuthClient {
|
|
191
|
+
getUser(): Promise<User | null>;
|
|
192
|
+
signInWithEmail(email: string, password: string): Promise<User>;
|
|
193
|
+
signInWithGoogle(redirectTo?: string): Promise<void>;
|
|
194
|
+
signOut(): Promise<void>;
|
|
195
|
+
onAuthStateChange(callback: (user: User | null) => void): () => void;
|
|
196
|
+
getSessionToken(): Promise<string | null>;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### The `User` Type
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
export interface User {
|
|
204
|
+
id: string;
|
|
205
|
+
email?: string;
|
|
206
|
+
avatarUrl?: string;
|
|
207
|
+
provider?: string;
|
|
208
|
+
metadata?: Record<string, any>;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Example: Directus Adapter
|
|
213
|
+
|
|
214
|
+
A custom Directus adapter that uses Directus SSO (e.g., Google OAuth through Directus) and optionally syncs sessions back to Supabase:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import type { AuthClient, User } from "@edcalderon/auth";
|
|
218
|
+
|
|
219
|
+
interface DirectusClientOptions {
|
|
220
|
+
directusUrl: string;
|
|
221
|
+
supabase?: any; // Optional: sync to Supabase as source of truth
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export class DirectusClient implements AuthClient {
|
|
225
|
+
private directusUrl: string;
|
|
226
|
+
private supabase: any;
|
|
227
|
+
private currentUser: User | null = null;
|
|
228
|
+
private listeners: Set<(user: User | null) => void> = new Set();
|
|
229
|
+
|
|
230
|
+
constructor(options: DirectusClientOptions) {
|
|
231
|
+
this.directusUrl = options.directusUrl;
|
|
232
|
+
this.supabase = options.supabase;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private mapUser(directusUser: any): User | null {
|
|
236
|
+
if (!directusUser) return null;
|
|
237
|
+
return {
|
|
238
|
+
id: directusUser.id,
|
|
239
|
+
email: directusUser.email,
|
|
240
|
+
avatarUrl: directusUser.avatar
|
|
241
|
+
? `${this.directusUrl}/assets/${directusUser.avatar}`
|
|
242
|
+
: undefined,
|
|
243
|
+
provider: "directus",
|
|
244
|
+
metadata: {
|
|
245
|
+
firstName: directusUser.first_name,
|
|
246
|
+
lastName: directusUser.last_name,
|
|
247
|
+
role: directusUser.role,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async getUser(): Promise<User | null> {
|
|
253
|
+
try {
|
|
254
|
+
const res = await fetch(`${this.directusUrl}/users/me`, {
|
|
255
|
+
credentials: "include",
|
|
256
|
+
});
|
|
257
|
+
if (!res.ok) return null;
|
|
258
|
+
const { data } = await res.json();
|
|
259
|
+
this.currentUser = this.mapUser(data);
|
|
260
|
+
return this.currentUser;
|
|
261
|
+
} catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async signInWithEmail(email: string, password: string): Promise<User> {
|
|
267
|
+
const res = await fetch(`${this.directusUrl}/auth/login`, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers: { "Content-Type": "application/json" },
|
|
270
|
+
credentials: "include",
|
|
271
|
+
body: JSON.stringify({ email, password }),
|
|
272
|
+
});
|
|
273
|
+
if (!res.ok) throw new Error("Directus login failed");
|
|
274
|
+
const user = await this.getUser();
|
|
275
|
+
if (!user) throw new Error("No user after login");
|
|
276
|
+
this.notifyListeners(user);
|
|
277
|
+
|
|
278
|
+
// Optional: sync to Supabase
|
|
279
|
+
if (this.supabase) {
|
|
280
|
+
await this.syncToSupabase(user);
|
|
281
|
+
}
|
|
282
|
+
return user;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async signInWithGoogle(redirectTo?: string): Promise<void> {
|
|
286
|
+
// Directus SSO β redirect to Directus Google OAuth endpoint
|
|
287
|
+
const callback = redirectTo || window.location.origin + "/auth/callback";
|
|
288
|
+
window.location.href =
|
|
289
|
+
`${this.directusUrl}/auth/login/google?redirect=${encodeURIComponent(callback)}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async signOut(): Promise<void> {
|
|
293
|
+
await fetch(`${this.directusUrl}/auth/logout`, {
|
|
294
|
+
method: "POST",
|
|
295
|
+
credentials: "include",
|
|
296
|
+
});
|
|
297
|
+
this.currentUser = null;
|
|
298
|
+
this.notifyListeners(null);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
onAuthStateChange(callback: (user: User | null) => void): () => void {
|
|
302
|
+
this.listeners.add(callback);
|
|
303
|
+
return () => { this.listeners.delete(callback); };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async getSessionToken(): Promise<string | null> {
|
|
307
|
+
try {
|
|
308
|
+
const res = await fetch(`${this.directusUrl}/auth/refresh`, {
|
|
309
|
+
method: "POST",
|
|
310
|
+
credentials: "include",
|
|
311
|
+
});
|
|
312
|
+
if (!res.ok) return null;
|
|
313
|
+
const { data } = await res.json();
|
|
314
|
+
return data?.access_token ?? null;
|
|
315
|
+
} catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private notifyListeners(user: User | null) {
|
|
321
|
+
this.listeners.forEach((cb) => cb(user));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private async syncToSupabase(user: User) {
|
|
325
|
+
// Sync user identity to Supabase as source of truth
|
|
326
|
+
// Implementation depends on your Supabase setup
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Usage:**
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
import { AuthProvider as UniversalAuthProvider } from "@edcalderon/auth";
|
|
335
|
+
import { DirectusClient } from "./adapters/DirectusClient";
|
|
336
|
+
|
|
337
|
+
const client = new DirectusClient({
|
|
338
|
+
directusUrl: "https://directus.example.com",
|
|
339
|
+
supabase: supabaseInstance, // optional sync
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
<UniversalAuthProvider client={client}>
|
|
343
|
+
<App />
|
|
344
|
+
</UniversalAuthProvider>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Example: Auth0 Adapter (Skeleton)
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import type { AuthClient, User } from "@edcalderon/auth";
|
|
351
|
+
|
|
352
|
+
export class Auth0Client implements AuthClient {
|
|
353
|
+
constructor(private auth0: any) {}
|
|
354
|
+
|
|
355
|
+
async getUser(): Promise<User | null> { /* ... */ }
|
|
356
|
+
async signInWithEmail(email: string, password: string): Promise<User> { /* ... */ }
|
|
357
|
+
async signInWithGoogle(redirectTo?: string): Promise<void> { /* ... */ }
|
|
358
|
+
async signOut(): Promise<void> { /* ... */ }
|
|
359
|
+
onAuthStateChange(callback: (user: User | null) => void): () => void { /* ... */ }
|
|
360
|
+
async getSessionToken(): Promise<string | null> { /* ... */ }
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
By implementing the `AuthClient` interface, any provider fits into the same `<AuthProvider>` and `useAuth()` workflow β **zero changes** to your component tree.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Built-in Adapters
|
|
369
|
+
|
|
370
|
+
### `SupabaseClient`
|
|
371
|
+
|
|
372
|
+
Direct Supabase Auth adapter. Uses `@supabase/supabase-js` for session management, OAuth, and email/password.
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { SupabaseClient } from "@edcalderon/auth";
|
|
376
|
+
import { createClient } from "@supabase/supabase-js";
|
|
377
|
+
|
|
378
|
+
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
|
379
|
+
const client = new SupabaseClient(supabase);
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**Features:**
|
|
383
|
+
- Email/password sign-in (`signInWithPassword`)
|
|
384
|
+
- Google OAuth (`signInWithOAuth`)
|
|
385
|
+
- Session token via `getSession().access_token`
|
|
386
|
+
- Real-time auth state changes via `onAuthStateChange`
|
|
387
|
+
|
|
388
|
+
### `FirebaseClient`
|
|
389
|
+
|
|
390
|
+
Firebase-only adapter. Uses Firebase Auth methods via dependency injection (tree-shaking friendly).
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { FirebaseClient } from "@edcalderon/auth";
|
|
394
|
+
import { getAuth, GoogleAuthProvider, signInWithEmailAndPassword, signInWithPopup, signOut, onAuthStateChanged } from "firebase/auth";
|
|
395
|
+
|
|
396
|
+
const auth = getAuth(app);
|
|
397
|
+
const client = new FirebaseClient(auth, {
|
|
398
|
+
signInWithEmailAndPassword,
|
|
399
|
+
signInWithPopup,
|
|
400
|
+
signOut,
|
|
401
|
+
onAuthStateChanged,
|
|
402
|
+
}, new GoogleAuthProvider());
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**Features:**
|
|
406
|
+
- Email/password sign-in
|
|
407
|
+
- Google popup sign-in
|
|
408
|
+
- Firebase ID token via `getIdToken()`
|
|
409
|
+
- Real-time auth state changes
|
|
410
|
+
|
|
411
|
+
### `HybridClient`
|
|
412
|
+
|
|
413
|
+
Bridges Firebase Google popup β Supabase `signInWithIdToken`. Perfect for apps that need Firebase's popup UX but Supabase as the data backend.
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
import { HybridClient } from "@edcalderon/auth";
|
|
417
|
+
|
|
418
|
+
const client = new HybridClient({
|
|
419
|
+
supabase,
|
|
420
|
+
firebaseAuth: auth,
|
|
421
|
+
firebaseMethods: { signInWithPopup, signOut, credentialFromResult: GoogleAuthProvider.credentialFromResult },
|
|
422
|
+
googleProvider: new GoogleAuthProvider(),
|
|
423
|
+
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**Features:**
|
|
427
|
+
- Firebase popup β extracts Google OIDC ID token β passes to Supabase `signInWithIdToken`
|
|
428
|
+
- Graceful fallback to Supabase native OAuth when Firebase is not configured
|
|
429
|
+
- Dual sign-out (Firebase + Supabase)
|
|
430
|
+
- Auth state tracked via Supabase session
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## API Reference
|
|
435
|
+
|
|
436
|
+
### `<AuthProvider>`
|
|
437
|
+
|
|
438
|
+
React context provider that wraps your app with authentication state.
|
|
439
|
+
|
|
440
|
+
```tsx
|
|
441
|
+
<AuthProvider client={authClient}>
|
|
442
|
+
{children}
|
|
443
|
+
</AuthProvider>
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
| Prop | Type | Description |
|
|
447
|
+
|------|------|-------------|
|
|
448
|
+
| `client` | `AuthClient` | The authentication adapter instance |
|
|
449
|
+
| `children` | `ReactNode` | Child components |
|
|
450
|
+
|
|
451
|
+
### `useAuth()`
|
|
452
|
+
|
|
453
|
+
React hook that returns the current authentication state and actions.
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
const {
|
|
457
|
+
user, // User | null
|
|
458
|
+
loading, // boolean
|
|
459
|
+
error, // string | null
|
|
460
|
+
client, // AuthClient (direct access)
|
|
461
|
+
signInWithEmail, // (email: string, password: string) => Promise<User>
|
|
462
|
+
signInWithGoogle, // (redirectTo?: string) => Promise<void>
|
|
463
|
+
signOutUser, // () => Promise<void>
|
|
464
|
+
} = useAuth();
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
> **Note:** `useAuth()` must be called within an `<AuthProvider>`. It will throw if used outside the provider tree.
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## Publishing & Releases
|
|
472
|
+
|
|
473
|
+
### Automated NPM Publishing
|
|
474
|
+
|
|
475
|
+
This package uses GitHub Actions for automated publishing to NPM when version tags are created.
|
|
476
|
+
|
|
477
|
+
#### Release Process
|
|
478
|
+
|
|
479
|
+
1. **Update Version**: Bump the version in `package.json`
|
|
480
|
+
```bash
|
|
481
|
+
cd packages/auth
|
|
482
|
+
npm version patch # or minor, major
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
2. **Create Git Tag**: Create and push an `auth-v*` tag
|
|
486
|
+
```bash
|
|
487
|
+
git add packages/auth/package.json
|
|
488
|
+
git commit -m "chore(auth): bump version to X.Y.Z"
|
|
489
|
+
git tag auth-vX.Y.Z
|
|
490
|
+
git push && git push --tags
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
3. **Automated Publishing**: GitHub Actions will automatically build and publish to NPM
|
|
494
|
+
|
|
495
|
+
#### NPM Token Setup
|
|
496
|
+
|
|
497
|
+
To enable automated publishing:
|
|
498
|
+
|
|
499
|
+
1. Go to [NPM](https://www.npmjs.com/) β Access Tokens β Generate New Token
|
|
500
|
+
2. Create a token with **Automation** scope
|
|
501
|
+
3. Add to GitHub repository secrets as `NPM_TOKEN`
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## Documentation
|
|
506
|
+
|
|
507
|
+
- **[CHANGELOG](CHANGELOG.md)** β Version history and changes
|
|
508
|
+
- **[GitHub Releases](https://github.com/edcalderon/my-second-brain/releases)** β Tagged releases
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## License
|
|
513
|
+
|
|
514
|
+
MIT Β© Edward
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { AuthClient, User } from "./types";
|
|
3
|
+
interface AuthContextValue {
|
|
4
|
+
user: User | null;
|
|
5
|
+
loading: boolean;
|
|
6
|
+
error: string | null;
|
|
7
|
+
client: AuthClient;
|
|
8
|
+
signInWithEmail: (email: string, password: string) => Promise<User>;
|
|
9
|
+
signInWithGoogle: (redirectTo?: string) => Promise<void>;
|
|
10
|
+
signOutUser: () => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function AuthProvider({ client, children }: {
|
|
13
|
+
client: AuthClient;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export declare function useAuth(): AuthContextValue;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useEffect, useState, useMemo } from "react";
|
|
4
|
+
const AuthContext = createContext(undefined);
|
|
5
|
+
export function AuthProvider({ client, children }) {
|
|
6
|
+
const [user, setUser] = useState(null);
|
|
7
|
+
const [loading, setLoading] = useState(true);
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
let mounted = true;
|
|
11
|
+
async function initializeAuth() {
|
|
12
|
+
try {
|
|
13
|
+
const u = await client.getUser();
|
|
14
|
+
if (mounted) {
|
|
15
|
+
setUser(u);
|
|
16
|
+
setLoading(false);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (mounted) {
|
|
21
|
+
setError(err.message || "Failed to initialize auth");
|
|
22
|
+
setLoading(false);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
initializeAuth();
|
|
27
|
+
const unsubscribe = client.onAuthStateChange((newUser) => {
|
|
28
|
+
if (mounted) {
|
|
29
|
+
setUser(newUser);
|
|
30
|
+
setLoading(false);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return () => {
|
|
34
|
+
mounted = false;
|
|
35
|
+
unsubscribe();
|
|
36
|
+
};
|
|
37
|
+
}, [client]);
|
|
38
|
+
const value = useMemo(() => ({
|
|
39
|
+
user,
|
|
40
|
+
loading,
|
|
41
|
+
error,
|
|
42
|
+
client,
|
|
43
|
+
signInWithEmail: async (email, password) => {
|
|
44
|
+
setError(null);
|
|
45
|
+
try {
|
|
46
|
+
return await client.signInWithEmail(email, password);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
setError(err.message);
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
signInWithGoogle: async (redirectTo) => {
|
|
54
|
+
setError(null);
|
|
55
|
+
try {
|
|
56
|
+
await client.signInWithGoogle(redirectTo);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
setError(err.message);
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
signOutUser: async () => {
|
|
64
|
+
setError(null);
|
|
65
|
+
try {
|
|
66
|
+
await client.signOut();
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
setError(err.message);
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
}), [user, loading, error, client]);
|
|
74
|
+
return _jsx(AuthContext.Provider, { value: value, children: children });
|
|
75
|
+
}
|
|
76
|
+
export function useAuth() {
|
|
77
|
+
const context = useContext(AuthContext);
|
|
78
|
+
if (!context) {
|
|
79
|
+
throw new Error("useAuth must be used within AuthProvider");
|
|
80
|
+
}
|
|
81
|
+
return context;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=AuthProvider.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Auth } from "firebase/auth";
|
|
2
|
+
import type { GoogleAuthProvider as GoogleAuthProviderType } from "firebase/auth";
|
|
3
|
+
import { AuthClient, User } from "../types";
|
|
4
|
+
export interface FirebaseMethods {
|
|
5
|
+
signInWithEmailAndPassword: any;
|
|
6
|
+
signInWithPopup: any;
|
|
7
|
+
signOut: any;
|
|
8
|
+
onAuthStateChanged: any;
|
|
9
|
+
}
|
|
10
|
+
export declare class FirebaseClient implements AuthClient {
|
|
11
|
+
private auth;
|
|
12
|
+
private methods;
|
|
13
|
+
private googleProvider;
|
|
14
|
+
constructor(auth: Auth, methods: FirebaseMethods, googleProvider: GoogleAuthProviderType);
|
|
15
|
+
private mapUser;
|
|
16
|
+
getUser(): Promise<User | null>;
|
|
17
|
+
signInWithEmail(email: string, password: string): Promise<User>;
|
|
18
|
+
signInWithGoogle(redirectTo?: string): Promise<void>;
|
|
19
|
+
signOut(): Promise<void>;
|
|
20
|
+
onAuthStateChange(callback: (user: User | null) => void): () => void;
|
|
21
|
+
getSessionToken(): Promise<string | null>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export class FirebaseClient {
|
|
2
|
+
auth;
|
|
3
|
+
methods;
|
|
4
|
+
googleProvider;
|
|
5
|
+
constructor(auth, methods, googleProvider) {
|
|
6
|
+
this.auth = auth;
|
|
7
|
+
this.methods = methods;
|
|
8
|
+
this.googleProvider = googleProvider;
|
|
9
|
+
}
|
|
10
|
+
mapUser(user) {
|
|
11
|
+
if (!user)
|
|
12
|
+
return null;
|
|
13
|
+
return {
|
|
14
|
+
id: user.uid,
|
|
15
|
+
email: user.email || undefined,
|
|
16
|
+
avatarUrl: user.photoURL || undefined,
|
|
17
|
+
metadata: { displayName: user.displayName },
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async getUser() {
|
|
21
|
+
if (this.auth.currentUser)
|
|
22
|
+
return this.mapUser(this.auth.currentUser);
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
const unsubscribe = this.methods.onAuthStateChanged(this.auth, (user) => {
|
|
25
|
+
unsubscribe();
|
|
26
|
+
resolve(this.mapUser(user));
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async signInWithEmail(email, password) {
|
|
31
|
+
const userCredential = await this.methods.signInWithEmailAndPassword(this.auth, email, password);
|
|
32
|
+
return this.mapUser(userCredential.user);
|
|
33
|
+
}
|
|
34
|
+
async signInWithGoogle(redirectTo) {
|
|
35
|
+
await this.methods.signInWithPopup(this.auth, this.googleProvider);
|
|
36
|
+
}
|
|
37
|
+
async signOut() {
|
|
38
|
+
await this.methods.signOut(this.auth);
|
|
39
|
+
}
|
|
40
|
+
onAuthStateChange(callback) {
|
|
41
|
+
const unsubscribe = this.methods.onAuthStateChanged(this.auth, (user) => {
|
|
42
|
+
callback(this.mapUser(user));
|
|
43
|
+
});
|
|
44
|
+
return () => unsubscribe();
|
|
45
|
+
}
|
|
46
|
+
async getSessionToken() {
|
|
47
|
+
if (!this.auth.currentUser)
|
|
48
|
+
return null;
|
|
49
|
+
try {
|
|
50
|
+
return await this.auth.currentUser.getIdToken();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=FirebaseClient.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { SupabaseClient as SupabaseClientType } from "@supabase/supabase-js";
|
|
2
|
+
import type { Auth } from "firebase/auth";
|
|
3
|
+
import type { GoogleAuthProvider as GoogleAuthProviderType } from "firebase/auth";
|
|
4
|
+
import { AuthClient, User } from "../types";
|
|
5
|
+
export interface HybridFirebaseMethods {
|
|
6
|
+
signInWithPopup: any;
|
|
7
|
+
signOut: any;
|
|
8
|
+
credentialFromResult: any;
|
|
9
|
+
}
|
|
10
|
+
export interface HybridClientOptions {
|
|
11
|
+
supabase: SupabaseClientType;
|
|
12
|
+
firebaseAuth: Auth | null;
|
|
13
|
+
firebaseMethods: HybridFirebaseMethods | null;
|
|
14
|
+
googleProvider: GoogleAuthProviderType | null;
|
|
15
|
+
}
|
|
16
|
+
export declare class HybridClient implements AuthClient {
|
|
17
|
+
private supabase;
|
|
18
|
+
private firebaseAuth;
|
|
19
|
+
private methods;
|
|
20
|
+
private googleProvider;
|
|
21
|
+
constructor(options: HybridClientOptions);
|
|
22
|
+
private mapUser;
|
|
23
|
+
getUser(): Promise<User | null>;
|
|
24
|
+
signInWithEmail(email: string, password: string): Promise<User>;
|
|
25
|
+
signInWithGoogle(redirectTo?: string): Promise<void>;
|
|
26
|
+
signOut(): Promise<void>;
|
|
27
|
+
onAuthStateChange(callback: (user: User | null) => void): () => void;
|
|
28
|
+
getSessionToken(): Promise<string | null>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export class HybridClient {
|
|
2
|
+
supabase;
|
|
3
|
+
firebaseAuth;
|
|
4
|
+
methods;
|
|
5
|
+
googleProvider;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.supabase = options.supabase;
|
|
8
|
+
this.firebaseAuth = options.firebaseAuth;
|
|
9
|
+
this.methods = options.firebaseMethods;
|
|
10
|
+
this.googleProvider = options.googleProvider;
|
|
11
|
+
}
|
|
12
|
+
mapUser(user) {
|
|
13
|
+
if (!user)
|
|
14
|
+
return null;
|
|
15
|
+
return {
|
|
16
|
+
id: user.id,
|
|
17
|
+
email: user.email,
|
|
18
|
+
avatarUrl: user.user_metadata?.avatar_url || user.user_metadata?.picture,
|
|
19
|
+
provider: user.app_metadata?.provider || 'supabase',
|
|
20
|
+
metadata: user.user_metadata,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async getUser() {
|
|
24
|
+
const { data: { user }, error } = await this.supabase.auth.getUser();
|
|
25
|
+
if (error && error.message.includes("session"))
|
|
26
|
+
return null; // Safe fallback for unauthenticated
|
|
27
|
+
if (error)
|
|
28
|
+
throw error;
|
|
29
|
+
return this.mapUser(user);
|
|
30
|
+
}
|
|
31
|
+
async signInWithEmail(email, password) {
|
|
32
|
+
const { data, error } = await this.supabase.auth.signInWithPassword({
|
|
33
|
+
email,
|
|
34
|
+
password,
|
|
35
|
+
});
|
|
36
|
+
if (error)
|
|
37
|
+
throw error;
|
|
38
|
+
if (!data.user)
|
|
39
|
+
throw new Error("No user returned");
|
|
40
|
+
return this.mapUser(data.user);
|
|
41
|
+
}
|
|
42
|
+
async signInWithGoogle(redirectTo) {
|
|
43
|
+
if (!this.firebaseAuth || !this.methods || !this.googleProvider) {
|
|
44
|
+
console.warn("Firebase not configured, falling back to Supabase native OAuth");
|
|
45
|
+
const { error } = await this.supabase.auth.signInWithOAuth({
|
|
46
|
+
provider: "google",
|
|
47
|
+
options: {
|
|
48
|
+
redirectTo: redirectTo || (typeof window !== "undefined" ? window.location.origin : undefined),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
if (error)
|
|
52
|
+
throw error;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
// Firebase Google Popup
|
|
57
|
+
const userCredential = await this.methods.signInWithPopup(this.firebaseAuth, this.googleProvider);
|
|
58
|
+
// Generate Google OIDC ID token
|
|
59
|
+
const credential = this.methods.credentialFromResult(userCredential);
|
|
60
|
+
const idToken = credential?.idToken;
|
|
61
|
+
if (!idToken)
|
|
62
|
+
throw new Error("No Google ID Token found in credential");
|
|
63
|
+
// Pass parallel ID Token directly into Supabase
|
|
64
|
+
const { error: supaError } = await this.supabase.auth.signInWithIdToken({
|
|
65
|
+
provider: 'google',
|
|
66
|
+
token: idToken,
|
|
67
|
+
});
|
|
68
|
+
if (supaError)
|
|
69
|
+
throw supaError;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// Ensure if anything fails, we clean up the floating firebase session
|
|
73
|
+
if (this.firebaseAuth && this.methods) {
|
|
74
|
+
await this.methods.signOut(this.firebaseAuth).catch(() => { });
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async signOut() {
|
|
80
|
+
// Break both sessions parallel safely
|
|
81
|
+
if (this.firebaseAuth && this.methods) {
|
|
82
|
+
try {
|
|
83
|
+
await this.methods.signOut(this.firebaseAuth);
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
console.error("Firebase signout error:", e);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const { error } = await this.supabase.auth.signOut();
|
|
90
|
+
if (error)
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
onAuthStateChange(callback) {
|
|
94
|
+
const { data: { subscription } } = this.supabase.auth.onAuthStateChange((_event, session) => {
|
|
95
|
+
callback(this.mapUser(session?.user));
|
|
96
|
+
});
|
|
97
|
+
return () => {
|
|
98
|
+
subscription.unsubscribe();
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async getSessionToken() {
|
|
102
|
+
const { data: { session }, error } = await this.supabase.auth.getSession();
|
|
103
|
+
if (error || !session)
|
|
104
|
+
return null;
|
|
105
|
+
return session.access_token;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=HybridClient.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SupabaseClient as SupabaseClientType } from "@supabase/supabase-js";
|
|
2
|
+
import { AuthClient, User } from "../types";
|
|
3
|
+
export declare class SupabaseClient implements AuthClient {
|
|
4
|
+
private supabase;
|
|
5
|
+
constructor(supabase: SupabaseClientType);
|
|
6
|
+
private mapUser;
|
|
7
|
+
getUser(): Promise<User | null>;
|
|
8
|
+
signInWithEmail(email: string, password: string): Promise<User>;
|
|
9
|
+
signInWithGoogle(redirectTo?: string): Promise<void>;
|
|
10
|
+
signOut(): Promise<void>;
|
|
11
|
+
onAuthStateChange(callback: (user: User | null) => void): () => void;
|
|
12
|
+
getSessionToken(): Promise<string | null>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export class SupabaseClient {
|
|
2
|
+
supabase;
|
|
3
|
+
constructor(supabase) {
|
|
4
|
+
this.supabase = supabase;
|
|
5
|
+
}
|
|
6
|
+
mapUser(user) {
|
|
7
|
+
if (!user)
|
|
8
|
+
return null;
|
|
9
|
+
return {
|
|
10
|
+
id: user.id,
|
|
11
|
+
email: user.email,
|
|
12
|
+
avatarUrl: user.user_metadata?.avatar_url,
|
|
13
|
+
provider: user.app_metadata?.provider,
|
|
14
|
+
metadata: user.user_metadata,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async getUser() {
|
|
18
|
+
const { data: { user }, error } = await this.supabase.auth.getUser();
|
|
19
|
+
if (error)
|
|
20
|
+
throw error;
|
|
21
|
+
return this.mapUser(user);
|
|
22
|
+
}
|
|
23
|
+
async signInWithEmail(email, password) {
|
|
24
|
+
const { data, error } = await this.supabase.auth.signInWithPassword({
|
|
25
|
+
email,
|
|
26
|
+
password,
|
|
27
|
+
});
|
|
28
|
+
if (error)
|
|
29
|
+
throw error;
|
|
30
|
+
if (!data.user)
|
|
31
|
+
throw new Error("No user returned");
|
|
32
|
+
return this.mapUser(data.user);
|
|
33
|
+
}
|
|
34
|
+
async signInWithGoogle(redirectTo) {
|
|
35
|
+
const { error } = await this.supabase.auth.signInWithOAuth({
|
|
36
|
+
provider: "google",
|
|
37
|
+
options: {
|
|
38
|
+
redirectTo: redirectTo || (typeof window !== "undefined" ? window.location.origin : undefined),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
if (error)
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
async signOut() {
|
|
45
|
+
const { error } = await this.supabase.auth.signOut();
|
|
46
|
+
if (error)
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
onAuthStateChange(callback) {
|
|
50
|
+
const { data: { subscription } } = this.supabase.auth.onAuthStateChange((_event, session) => {
|
|
51
|
+
callback(this.mapUser(session?.user));
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
subscription.unsubscribe();
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async getSessionToken() {
|
|
58
|
+
const { data: { session }, error } = await this.supabase.auth.getSession();
|
|
59
|
+
if (error || !session)
|
|
60
|
+
return null;
|
|
61
|
+
return session.access_token;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=SupabaseClient.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface User {
|
|
2
|
+
id: string;
|
|
3
|
+
email?: string;
|
|
4
|
+
avatarUrl?: string;
|
|
5
|
+
provider?: string;
|
|
6
|
+
metadata?: Record<string, any>;
|
|
7
|
+
}
|
|
8
|
+
export interface AuthClient {
|
|
9
|
+
getUser(): Promise<User | null>;
|
|
10
|
+
signInWithEmail(email: string, password: string): Promise<User>;
|
|
11
|
+
signInWithGoogle(redirectTo?: string): Promise<void>;
|
|
12
|
+
signOut(): Promise<void>;
|
|
13
|
+
onAuthStateChange(callback: (user: User | null) => void): () => void;
|
|
14
|
+
getSessionToken(): Promise<string | null>;
|
|
15
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@edcalderon/auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A universal, provider-agnostic authentication orchestration package with extensible adapters for Supabase, Firebase, Directus, and custom OAuth providers",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"dev": "tsc --watch",
|
|
10
|
+
"prepublishOnly": "npm run build"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"auth",
|
|
14
|
+
"authentication",
|
|
15
|
+
"oauth",
|
|
16
|
+
"supabase",
|
|
17
|
+
"firebase",
|
|
18
|
+
"directus",
|
|
19
|
+
"provider-agnostic",
|
|
20
|
+
"react",
|
|
21
|
+
"sso"
|
|
22
|
+
],
|
|
23
|
+
"author": "Edward",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/edcalderon/my-second-brain.git",
|
|
31
|
+
"directory": "packages/auth"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@supabase/supabase-js": "^2.0.0",
|
|
35
|
+
"firebase": "^10.0.0 || ^11.0.0 || ^12.0.0",
|
|
36
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
37
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"@supabase/supabase-js": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"firebase": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/react": "^19.2.7",
|
|
49
|
+
"typescript": "^5.9.3"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|