@reauth-dev/sdk 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emil Suleymanov <emil@esnx.xyz>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,252 @@
1
+ # @reauth-dev/sdk
2
+
3
+ TypeScript SDK for [reauth.dev](https://reauth.dev) - passwordless authentication for your apps.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @reauth-dev/sdk
9
+ # or
10
+ pnpm add @reauth-dev/sdk
11
+ # or
12
+ yarn add @reauth-dev/sdk
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ### React (Next.js)
18
+
19
+ ```typescript
20
+ // app/layout.tsx
21
+ import { AuthProvider } from '@reauth-dev/sdk/react';
22
+
23
+ export default function RootLayout({ children }) {
24
+ return (
25
+ <AuthProvider config={{ domain: 'yourdomain.com' }}>
26
+ {children}
27
+ </AuthProvider>
28
+ );
29
+ }
30
+
31
+ // app/dashboard/page.tsx
32
+ import { ProtectedRoute, useAuthContext } from '@reauth-dev/sdk/react';
33
+
34
+ export default function Dashboard() {
35
+ return (
36
+ <ProtectedRoute>
37
+ <DashboardContent />
38
+ </ProtectedRoute>
39
+ );
40
+ }
41
+
42
+ function DashboardContent() {
43
+ const { user, logout } = useAuthContext();
44
+
45
+ return (
46
+ <div>
47
+ <h1>Welcome, {user?.email}</h1>
48
+ <button onClick={logout}>Sign out</button>
49
+ </div>
50
+ );
51
+ }
52
+ ```
53
+
54
+ ### Vanilla JavaScript
55
+
56
+ ```typescript
57
+ import { createReauthClient } from '@reauth-dev/sdk';
58
+
59
+ const reauth = createReauthClient({ domain: 'yourdomain.com' });
60
+
61
+ // Check if authenticated
62
+ const session = await reauth.getSession();
63
+ if (!session.valid) {
64
+ reauth.login(); // Redirect to login
65
+ }
66
+
67
+ // Log out
68
+ await reauth.logout();
69
+ ```
70
+
71
+ ### Server-Side (API Routes)
72
+
73
+ ```typescript
74
+ // Next.js API Route
75
+ import { createServerClient } from '@reauth-dev/sdk/server';
76
+ import { NextRequest, NextResponse } from 'next/server';
77
+
78
+ const reauth = createServerClient({ domain: 'yourdomain.com' });
79
+
80
+ export async function GET(request: NextRequest) {
81
+ const user = await reauth.getUser(request.headers.get('cookie') || '');
82
+
83
+ if (!user) {
84
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
85
+ }
86
+
87
+ return NextResponse.json({ user });
88
+ }
89
+ ```
90
+
91
+ ## How It Works
92
+
93
+ reauth.dev handles the entire login flow. Your app just needs to:
94
+
95
+ 1. **Redirect to login** - Send users to `https://reauth.yourdomain.com`
96
+ 2. **Check session** - Verify authentication via the session endpoint
97
+ 3. **Handle logout** - Clear session cookies
98
+
99
+ ```
100
+ User clicks "Sign in" → Redirect to reauth.yourdomain.com
101
+ → reauth handles email + magic link → User authenticates
102
+ → Redirect back to your app with cookies set
103
+ → Your app checks /auth/session → User info returned
104
+ ```
105
+
106
+ ## API Reference
107
+
108
+ ### Browser Client
109
+
110
+ ```typescript
111
+ import { createReauthClient } from '@reauth-dev/sdk';
112
+
113
+ const reauth = createReauthClient({ domain: 'yourdomain.com' });
114
+
115
+ // Redirect to login page
116
+ reauth.login();
117
+
118
+ // Check authentication status
119
+ const session = await reauth.getSession();
120
+ // Returns: { valid, end_user_id, email, roles, waitlist_position, error, error_code }
121
+
122
+ // Refresh access token (when session.valid === false)
123
+ const success = await reauth.refresh();
124
+
125
+ // Log out
126
+ await reauth.logout();
127
+
128
+ // Delete account (self-service)
129
+ const deleted = await reauth.deleteAccount();
130
+ ```
131
+
132
+ ### Server Client
133
+
134
+ ```typescript
135
+ import { createServerClient } from '@reauth-dev/sdk/server';
136
+
137
+ const reauth = createServerClient({ domain: 'yourdomain.com' });
138
+
139
+ // Get raw session data
140
+ const session = await reauth.getSession(cookieHeader);
141
+
142
+ // Get user object (or null if not authenticated)
143
+ const user = await reauth.getUser(cookieHeader);
144
+ // Returns: { id, email, roles } | null
145
+ ```
146
+
147
+ ### React Components
148
+
149
+ ```typescript
150
+ import { AuthProvider, useAuthContext, useAuth, ProtectedRoute } from '@reauth-dev/sdk/react';
151
+
152
+ // AuthProvider - Wrap your app
153
+ <AuthProvider config={{ domain: 'yourdomain.com' }}>
154
+ {children}
155
+ </AuthProvider>
156
+
157
+ // useAuthContext - Access auth state
158
+ const { user, loading, error, isOnWaitlist, waitlistPosition, login, logout, refetch } = useAuthContext();
159
+
160
+ // useAuth - Standalone hook (without provider)
161
+ const auth = useAuth({ domain: 'yourdomain.com' });
162
+
163
+ // ProtectedRoute - Protect pages
164
+ <ProtectedRoute fallback={<Loading />} onWaitlist={() => router.push('/waitlist')}>
165
+ <ProtectedContent />
166
+ </ProtectedRoute>
167
+ ```
168
+
169
+ ## Types
170
+
171
+ ```typescript
172
+ type ReauthSession = {
173
+ valid: boolean;
174
+ end_user_id: string | null;
175
+ email: string | null;
176
+ roles: string[] | null;
177
+ waitlist_position: number | null;
178
+ error: string | null;
179
+ error_code: 'ACCOUNT_SUSPENDED' | null;
180
+ };
181
+
182
+ type User = {
183
+ id: string;
184
+ email: string;
185
+ roles: string[];
186
+ };
187
+
188
+ type ReauthConfig = {
189
+ domain: string;
190
+ };
191
+ ```
192
+
193
+ ## Handling Edge Cases
194
+
195
+ ### Token Refresh
196
+
197
+ ```typescript
198
+ const session = await reauth.getSession();
199
+
200
+ if (!session.valid && !session.error_code) {
201
+ // Access token expired, try refresh
202
+ const refreshed = await reauth.refresh();
203
+ if (refreshed) {
204
+ const newSession = await reauth.getSession();
205
+ // Continue with newSession
206
+ } else {
207
+ // Refresh token also expired, redirect to login
208
+ reauth.login();
209
+ }
210
+ }
211
+ ```
212
+
213
+ ### Waitlist
214
+
215
+ ```typescript
216
+ if (session.valid && session.waitlist_position) {
217
+ // User authenticated but on waitlist
218
+ // Show waitlist page with position
219
+ console.log(`Position: #${session.waitlist_position}`);
220
+ }
221
+ ```
222
+
223
+ ### Account Suspended
224
+
225
+ ```typescript
226
+ if (session.error_code === 'ACCOUNT_SUSPENDED') {
227
+ // Show suspended message
228
+ // User cannot access app until admin unfreezes
229
+ }
230
+ ```
231
+
232
+ ## Prerequisites
233
+
234
+ 1. **Verify your domain** on [reauth.dev](https://reauth.dev)
235
+ 2. **Set redirect URL** (where users go after login)
236
+ 3. **Enable magic link auth** in domain settings
237
+
238
+ ## Cookies
239
+
240
+ reauth.dev sets these cookies on `.yourdomain.com`:
241
+
242
+ | Cookie | Purpose | Expiry |
243
+ |--------|---------|--------|
244
+ | `end_user_access_token` | Auth token (HttpOnly) | 24 hours |
245
+ | `end_user_refresh_token` | Token renewal (HttpOnly) | 30 days |
246
+ | `end_user_email` | Display email | 30 days |
247
+
248
+ All cookies require HTTPS.
249
+
250
+ ## License
251
+
252
+ MIT
@@ -0,0 +1,269 @@
1
+ // src/client.ts
2
+ function createReauthClient(config) {
3
+ const { domain } = config;
4
+ const baseUrl = `https://reauth.${domain}/api/public`;
5
+ return {
6
+ /**
7
+ * Redirect the user to the reauth.dev login page.
8
+ * After successful login, they'll be redirected back to your configured redirect URL.
9
+ */
10
+ login() {
11
+ if (typeof window === "undefined") {
12
+ throw new Error("login() can only be called in browser");
13
+ }
14
+ window.location.href = `https://reauth.${domain}/`;
15
+ },
16
+ /**
17
+ * Check if the user is authenticated.
18
+ * Returns session info including user ID, email, and roles.
19
+ */
20
+ async getSession() {
21
+ const res = await fetch(`${baseUrl}/auth/session`, {
22
+ credentials: "include"
23
+ });
24
+ return res.json();
25
+ },
26
+ /**
27
+ * Refresh the access token using the refresh token.
28
+ * Call this when getSession() returns valid: false but no error_code.
29
+ * @returns true if refresh succeeded, false otherwise
30
+ */
31
+ async refresh() {
32
+ const res = await fetch(`${baseUrl}/auth/refresh`, {
33
+ method: "POST",
34
+ credentials: "include"
35
+ });
36
+ return res.ok;
37
+ },
38
+ /**
39
+ * Get an access token for Bearer authentication.
40
+ * Use this when calling your own API that uses local token verification.
41
+ *
42
+ * @returns TokenResponse with access token, or null if not authenticated
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const tokenResponse = await reauth.getToken();
47
+ * if (tokenResponse) {
48
+ * fetch('/api/data', {
49
+ * headers: { Authorization: `Bearer ${tokenResponse.accessToken}` }
50
+ * });
51
+ * }
52
+ * ```
53
+ */
54
+ async getToken() {
55
+ try {
56
+ const res = await fetch(`${baseUrl}/auth/token`, {
57
+ method: "GET",
58
+ credentials: "include"
59
+ });
60
+ if (!res.ok) {
61
+ if (res.status === 401) return null;
62
+ throw new Error(`Failed to get token: ${res.status}`);
63
+ }
64
+ const data = await res.json();
65
+ return {
66
+ accessToken: data.access_token,
67
+ expiresIn: data.expires_in,
68
+ tokenType: data.token_type
69
+ };
70
+ } catch {
71
+ return null;
72
+ }
73
+ },
74
+ /**
75
+ * Log out the user by clearing all session cookies.
76
+ */
77
+ async logout() {
78
+ await fetch(`${baseUrl}/auth/logout`, {
79
+ method: "POST",
80
+ credentials: "include"
81
+ });
82
+ },
83
+ /**
84
+ * Delete the user's own account (self-service).
85
+ * @returns true if deletion succeeded, false otherwise
86
+ */
87
+ async deleteAccount() {
88
+ const res = await fetch(`${baseUrl}/auth/account`, {
89
+ method: "DELETE",
90
+ credentials: "include"
91
+ });
92
+ return res.ok;
93
+ },
94
+ // ========================================================================
95
+ // Billing Methods
96
+ // ========================================================================
97
+ /**
98
+ * Get available subscription plans for the domain.
99
+ * Only returns public plans sorted by display order.
100
+ */
101
+ async getPlans() {
102
+ const res = await fetch(`${baseUrl}/billing/plans`, {
103
+ credentials: "include"
104
+ });
105
+ if (!res.ok) return [];
106
+ const data = await res.json();
107
+ return data.map(
108
+ (p) => ({
109
+ id: p.id,
110
+ code: p.code,
111
+ name: p.name,
112
+ description: p.description,
113
+ priceCents: p.price_cents,
114
+ currency: p.currency,
115
+ interval: p.interval,
116
+ intervalCount: p.interval_count,
117
+ trialDays: p.trial_days,
118
+ features: p.features,
119
+ displayOrder: p.display_order
120
+ })
121
+ );
122
+ },
123
+ /**
124
+ * Get the current user's subscription status.
125
+ */
126
+ async getSubscription() {
127
+ const res = await fetch(`${baseUrl}/billing/subscription`, {
128
+ credentials: "include"
129
+ });
130
+ const data = await res.json();
131
+ return {
132
+ id: data.id,
133
+ planCode: data.plan_code,
134
+ planName: data.plan_name,
135
+ status: data.status,
136
+ currentPeriodEnd: data.current_period_end,
137
+ trialEnd: data.trial_end,
138
+ cancelAtPeriodEnd: data.cancel_at_period_end
139
+ };
140
+ },
141
+ /**
142
+ * Create a Stripe checkout session to subscribe to a plan.
143
+ * @param planCode The plan code to subscribe to
144
+ * @param successUrl URL to redirect to after successful payment
145
+ * @param cancelUrl URL to redirect to if checkout is canceled
146
+ * @returns Checkout session with URL to redirect the user to
147
+ */
148
+ async createCheckout(planCode, successUrl, cancelUrl) {
149
+ const res = await fetch(`${baseUrl}/billing/checkout`, {
150
+ method: "POST",
151
+ headers: { "Content-Type": "application/json" },
152
+ credentials: "include",
153
+ body: JSON.stringify({
154
+ plan_code: planCode,
155
+ success_url: successUrl,
156
+ cancel_url: cancelUrl
157
+ })
158
+ });
159
+ if (!res.ok) {
160
+ const err = await res.json().catch(() => ({}));
161
+ throw new Error(err.message || "Failed to create checkout session");
162
+ }
163
+ const data = await res.json();
164
+ return { checkoutUrl: data.checkout_url };
165
+ },
166
+ /**
167
+ * Redirect user to subscribe to a plan.
168
+ * Creates a checkout session and redirects to Stripe.
169
+ * @param planCode The plan code to subscribe to
170
+ */
171
+ async subscribe(planCode) {
172
+ if (typeof window === "undefined") {
173
+ throw new Error("subscribe() can only be called in browser");
174
+ }
175
+ const currentUrl = window.location.href;
176
+ const { checkoutUrl } = await this.createCheckout(
177
+ planCode,
178
+ currentUrl,
179
+ currentUrl
180
+ );
181
+ window.location.href = checkoutUrl;
182
+ },
183
+ /**
184
+ * Open the Stripe customer portal for managing subscription.
185
+ * @param returnUrl URL to return to after leaving the portal
186
+ */
187
+ async openBillingPortal(returnUrl) {
188
+ if (typeof window === "undefined") {
189
+ throw new Error("openBillingPortal() can only be called in browser");
190
+ }
191
+ const res = await fetch(`${baseUrl}/billing/portal`, {
192
+ method: "POST",
193
+ headers: { "Content-Type": "application/json" },
194
+ credentials: "include",
195
+ body: JSON.stringify({
196
+ return_url: returnUrl || window.location.href
197
+ })
198
+ });
199
+ if (!res.ok) {
200
+ throw new Error("Failed to open billing portal");
201
+ }
202
+ const data = await res.json();
203
+ window.location.href = data.portal_url;
204
+ },
205
+ /**
206
+ * Cancel the user's subscription at period end.
207
+ * @returns true if cancellation succeeded
208
+ */
209
+ async cancelSubscription() {
210
+ const res = await fetch(`${baseUrl}/billing/cancel`, {
211
+ method: "POST",
212
+ credentials: "include"
213
+ });
214
+ return res.ok;
215
+ },
216
+ // ========================================================================
217
+ // Balance Methods
218
+ // ========================================================================
219
+ /**
220
+ * Get the current user's balance.
221
+ * @returns Object with the current balance
222
+ */
223
+ async getBalance() {
224
+ const res = await fetch(`${baseUrl}/balance`, {
225
+ credentials: "include"
226
+ });
227
+ if (!res.ok) {
228
+ if (res.status === 401) throw new Error("Not authenticated");
229
+ throw new Error(`Failed to get balance: ${res.status}`);
230
+ }
231
+ return res.json();
232
+ },
233
+ /**
234
+ * Get the current user's balance transaction history.
235
+ * @param opts - Optional pagination (limit, offset)
236
+ * @returns Object with array of transactions (newest first)
237
+ */
238
+ async getTransactions(opts) {
239
+ const params = new URLSearchParams();
240
+ if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
241
+ if (opts?.offset !== void 0) params.set("offset", String(opts.offset));
242
+ const qs = params.toString();
243
+ const res = await fetch(
244
+ `${baseUrl}/balance/transactions${qs ? `?${qs}` : ""}`,
245
+ { credentials: "include" }
246
+ );
247
+ if (!res.ok) {
248
+ if (res.status === 401) throw new Error("Not authenticated");
249
+ throw new Error(`Failed to get transactions: ${res.status}`);
250
+ }
251
+ const data = await res.json();
252
+ return {
253
+ transactions: data.transactions.map(
254
+ (t) => ({
255
+ id: t.id,
256
+ amountDelta: t.amount_delta,
257
+ reason: t.reason,
258
+ balanceAfter: t.balance_after,
259
+ createdAt: t.created_at
260
+ })
261
+ )
262
+ };
263
+ }
264
+ };
265
+ }
266
+
267
+ export {
268
+ createReauthClient
269
+ };
@@ -0,0 +1,127 @@
1
+ import { R as ReauthConfig, a as ReauthSession, T as TokenResponse, S as SubscriptionPlan, U as UserSubscription, C as CheckoutSession, b as TransactionsPaginationOptions, B as BalanceTransaction } from './types-D8oOYbeC.mjs';
2
+ export { A as AuthResult, h as ChargeOptions, i as DepositOptions, D as DomainEndUserClaims, e as ReauthServerConfig, f as RequestLike, g as SubscriptionInfo, c as User, d as UserDetails } from './types-D8oOYbeC.mjs';
3
+
4
+ /**
5
+ * Create a reauth client for browser-side authentication.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const reauth = createReauthClient({ domain: 'yourdomain.com' });
10
+ *
11
+ * // Check if user is authenticated
12
+ * const session = await reauth.getSession();
13
+ * if (!session.valid) {
14
+ * reauth.login(); // Redirect to login page
15
+ * }
16
+ *
17
+ * // Log out
18
+ * await reauth.logout();
19
+ * ```
20
+ */
21
+ declare function createReauthClient(config: ReauthConfig): {
22
+ /**
23
+ * Redirect the user to the reauth.dev login page.
24
+ * After successful login, they'll be redirected back to your configured redirect URL.
25
+ */
26
+ login(): void;
27
+ /**
28
+ * Check if the user is authenticated.
29
+ * Returns session info including user ID, email, and roles.
30
+ */
31
+ getSession(): Promise<ReauthSession>;
32
+ /**
33
+ * Refresh the access token using the refresh token.
34
+ * Call this when getSession() returns valid: false but no error_code.
35
+ * @returns true if refresh succeeded, false otherwise
36
+ */
37
+ refresh(): Promise<boolean>;
38
+ /**
39
+ * Get an access token for Bearer authentication.
40
+ * Use this when calling your own API that uses local token verification.
41
+ *
42
+ * @returns TokenResponse with access token, or null if not authenticated
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const tokenResponse = await reauth.getToken();
47
+ * if (tokenResponse) {
48
+ * fetch('/api/data', {
49
+ * headers: { Authorization: `Bearer ${tokenResponse.accessToken}` }
50
+ * });
51
+ * }
52
+ * ```
53
+ */
54
+ getToken(): Promise<TokenResponse | null>;
55
+ /**
56
+ * Log out the user by clearing all session cookies.
57
+ */
58
+ logout(): Promise<void>;
59
+ /**
60
+ * Delete the user's own account (self-service).
61
+ * @returns true if deletion succeeded, false otherwise
62
+ */
63
+ deleteAccount(): Promise<boolean>;
64
+ /**
65
+ * Get available subscription plans for the domain.
66
+ * Only returns public plans sorted by display order.
67
+ */
68
+ getPlans(): Promise<SubscriptionPlan[]>;
69
+ /**
70
+ * Get the current user's subscription status.
71
+ */
72
+ getSubscription(): Promise<UserSubscription>;
73
+ /**
74
+ * Create a Stripe checkout session to subscribe to a plan.
75
+ * @param planCode The plan code to subscribe to
76
+ * @param successUrl URL to redirect to after successful payment
77
+ * @param cancelUrl URL to redirect to if checkout is canceled
78
+ * @returns Checkout session with URL to redirect the user to
79
+ */
80
+ createCheckout(planCode: string, successUrl: string, cancelUrl: string): Promise<CheckoutSession>;
81
+ /**
82
+ * Redirect user to subscribe to a plan.
83
+ * Creates a checkout session and redirects to Stripe.
84
+ * @param planCode The plan code to subscribe to
85
+ */
86
+ subscribe(planCode: string): Promise<void>;
87
+ /**
88
+ * Open the Stripe customer portal for managing subscription.
89
+ * @param returnUrl URL to return to after leaving the portal
90
+ */
91
+ openBillingPortal(returnUrl?: string): Promise<void>;
92
+ /**
93
+ * Cancel the user's subscription at period end.
94
+ * @returns true if cancellation succeeded
95
+ */
96
+ cancelSubscription(): Promise<boolean>;
97
+ /**
98
+ * Get the current user's balance.
99
+ * @returns Object with the current balance
100
+ */
101
+ getBalance(): Promise<{
102
+ balance: number;
103
+ }>;
104
+ /**
105
+ * Get the current user's balance transaction history.
106
+ * @param opts - Optional pagination (limit, offset)
107
+ * @returns Object with array of transactions (newest first)
108
+ */
109
+ getTransactions(opts?: TransactionsPaginationOptions): Promise<{
110
+ transactions: BalanceTransaction[];
111
+ }>;
112
+ };
113
+ type ReauthClient = ReturnType<typeof createReauthClient>;
114
+
115
+ declare enum ReauthErrorCode {
116
+ /**
117
+ * OAuth retry window expired. The OAuth flow must be restarted.
118
+ */
119
+ OAUTH_RETRY_EXPIRED = "OAUTH_RETRY_EXPIRED"
120
+ }
121
+ type ReauthError = {
122
+ code: ReauthErrorCode | string;
123
+ message?: string;
124
+ };
125
+ declare function requiresOAuthRestart(error: ReauthError | null | undefined): boolean;
126
+
127
+ export { BalanceTransaction, type ReauthClient, ReauthConfig, type ReauthError, ReauthErrorCode, ReauthSession, TokenResponse, TransactionsPaginationOptions, createReauthClient, requiresOAuthRestart };