@pixygon/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/README.md +267 -0
- package/dist/chunk-E34M2RJD.mjs +1003 -0
- package/dist/components/index.d.mts +14 -0
- package/dist/components/index.d.ts +14 -0
- package/dist/components/index.js +1720 -0
- package/dist/components/index.mjs +1646 -0
- package/dist/index-CIK2MKl9.d.mts +201 -0
- package/dist/index-CIK2MKl9.d.ts +201 -0
- package/dist/index.d.mts +233 -0
- package/dist/index.d.ts +233 -0
- package/dist/index.js +1037 -0
- package/dist/index.mjs +28 -0
- package/package.json +58 -0
- package/src/api/client.ts +358 -0
- package/src/components/ForgotPasswordForm.tsx +410 -0
- package/src/components/LoginForm.tsx +323 -0
- package/src/components/PixygonAuth.tsx +170 -0
- package/src/components/RegisterForm.tsx +463 -0
- package/src/components/VerifyForm.tsx +411 -0
- package/src/components/index.ts +40 -0
- package/src/components/styles.ts +282 -0
- package/src/hooks/index.ts +284 -0
- package/src/hooks/useProfileSync.ts +201 -0
- package/src/index.ts +99 -0
- package/src/providers/AuthProvider.tsx +480 -0
- package/src/types/index.ts +310 -0
- package/src/utils/storage.ts +167 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pixygon/auth - Shared Types
|
|
3
|
+
* Type definitions for the Pixygon authentication system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// User Types
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
export type UserRole = 'user' | 'creator' | 'admin' | 'superadmin';
|
|
11
|
+
|
|
12
|
+
export type SubscriptionTier = 'free' | 'basic' | 'pro' | 'family' | 'enterprise';
|
|
13
|
+
|
|
14
|
+
export interface User {
|
|
15
|
+
_id: string;
|
|
16
|
+
userName: string;
|
|
17
|
+
email: string;
|
|
18
|
+
isVerified: boolean;
|
|
19
|
+
role: UserRole;
|
|
20
|
+
|
|
21
|
+
// Profile
|
|
22
|
+
firstName?: string;
|
|
23
|
+
lastName?: string;
|
|
24
|
+
profilePicture?: string;
|
|
25
|
+
|
|
26
|
+
// Subscription
|
|
27
|
+
subscriptionTier?: SubscriptionTier;
|
|
28
|
+
stripeCustomerId?: string;
|
|
29
|
+
|
|
30
|
+
// Token system
|
|
31
|
+
dailyTokens?: number;
|
|
32
|
+
purchasedTokens?: number;
|
|
33
|
+
bonusTokens?: number;
|
|
34
|
+
subscriptionTokens?: number;
|
|
35
|
+
|
|
36
|
+
// Parental controls (for family plans)
|
|
37
|
+
isChildAccount?: boolean;
|
|
38
|
+
parentUserId?: string;
|
|
39
|
+
|
|
40
|
+
// API access
|
|
41
|
+
apiAccess?: boolean;
|
|
42
|
+
|
|
43
|
+
// Social links
|
|
44
|
+
discordUserId?: string;
|
|
45
|
+
twitchUser?: string;
|
|
46
|
+
|
|
47
|
+
// Timestamps
|
|
48
|
+
createdAt?: string;
|
|
49
|
+
updatedAt?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Token Types
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
export interface TokenPair {
|
|
57
|
+
accessToken: string;
|
|
58
|
+
refreshToken: string;
|
|
59
|
+
expiresIn: number; // seconds until access token expires
|
|
60
|
+
refreshExpiresIn: number; // seconds until refresh token expires
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface TokenPayload {
|
|
64
|
+
id: string;
|
|
65
|
+
role?: UserRole;
|
|
66
|
+
iat: number;
|
|
67
|
+
exp: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// Auth State Types
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
export type AuthStatus =
|
|
75
|
+
| 'idle' // Initial state
|
|
76
|
+
| 'loading' // Checking auth status
|
|
77
|
+
| 'authenticated' // User is logged in
|
|
78
|
+
| 'unauthenticated' // No valid session
|
|
79
|
+
| 'verifying'; // Email verification pending
|
|
80
|
+
|
|
81
|
+
export interface AuthState {
|
|
82
|
+
user: User | null;
|
|
83
|
+
accessToken: string | null;
|
|
84
|
+
refreshToken: string | null;
|
|
85
|
+
status: AuthStatus;
|
|
86
|
+
isLoading: boolean;
|
|
87
|
+
error: AuthError | null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Auth Request/Response Types
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
export interface LoginRequest {
|
|
95
|
+
userName: string; // Can be username or email
|
|
96
|
+
password: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface LoginResponse {
|
|
100
|
+
user: User;
|
|
101
|
+
token: string;
|
|
102
|
+
refreshToken?: string;
|
|
103
|
+
expiresIn?: number;
|
|
104
|
+
message?: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface RegisterRequest {
|
|
108
|
+
userName: string;
|
|
109
|
+
email: string;
|
|
110
|
+
password: string;
|
|
111
|
+
confirmPassword?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface RegisterResponse {
|
|
115
|
+
user: User;
|
|
116
|
+
message?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface VerifyRequest {
|
|
120
|
+
userName: string;
|
|
121
|
+
verificationCode: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface VerifyResponse {
|
|
125
|
+
user: User;
|
|
126
|
+
token: string;
|
|
127
|
+
refreshToken?: string;
|
|
128
|
+
message?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface ForgotPasswordRequest {
|
|
132
|
+
email: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface ForgotPasswordResponse {
|
|
136
|
+
message: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface RecoverPasswordRequest {
|
|
140
|
+
email: string;
|
|
141
|
+
recoveryCode: string;
|
|
142
|
+
newPassword: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface RecoverPasswordResponse {
|
|
146
|
+
message: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface RefreshTokenRequest {
|
|
150
|
+
refreshToken: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface RefreshTokenResponse {
|
|
154
|
+
token: string;
|
|
155
|
+
refreshToken: string;
|
|
156
|
+
expiresIn: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface ResendVerificationRequest {
|
|
160
|
+
userName: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface ResendVerificationResponse {
|
|
164
|
+
message: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ============================================================================
|
|
168
|
+
// Error Types
|
|
169
|
+
// ============================================================================
|
|
170
|
+
|
|
171
|
+
export type AuthErrorCode =
|
|
172
|
+
| 'INVALID_CREDENTIALS'
|
|
173
|
+
| 'USER_NOT_FOUND'
|
|
174
|
+
| 'USER_EXISTS'
|
|
175
|
+
| 'EMAIL_NOT_VERIFIED'
|
|
176
|
+
| 'INVALID_VERIFICATION_CODE'
|
|
177
|
+
| 'EXPIRED_VERIFICATION_CODE'
|
|
178
|
+
| 'INVALID_RECOVERY_CODE'
|
|
179
|
+
| 'EXPIRED_RECOVERY_CODE'
|
|
180
|
+
| 'TOKEN_EXPIRED'
|
|
181
|
+
| 'TOKEN_INVALID'
|
|
182
|
+
| 'REFRESH_TOKEN_EXPIRED'
|
|
183
|
+
| 'NETWORK_ERROR'
|
|
184
|
+
| 'SERVER_ERROR'
|
|
185
|
+
| 'UNKNOWN_ERROR';
|
|
186
|
+
|
|
187
|
+
export interface AuthError {
|
|
188
|
+
code: AuthErrorCode;
|
|
189
|
+
message: string;
|
|
190
|
+
details?: Record<string, unknown>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// Configuration Types
|
|
195
|
+
// ============================================================================
|
|
196
|
+
|
|
197
|
+
export interface AuthConfig {
|
|
198
|
+
/** Base URL for the auth API (e.g., https://pixygon-server.onrender.com/v1) */
|
|
199
|
+
baseUrl: string;
|
|
200
|
+
|
|
201
|
+
/** App identifier for token storage keys */
|
|
202
|
+
appId: string;
|
|
203
|
+
|
|
204
|
+
/** App display name for branded components */
|
|
205
|
+
appName?: string;
|
|
206
|
+
|
|
207
|
+
/** Enable automatic token refresh (default: true) */
|
|
208
|
+
autoRefresh?: boolean;
|
|
209
|
+
|
|
210
|
+
/** Time before expiry to trigger refresh in seconds (default: 300 = 5 min) */
|
|
211
|
+
refreshThreshold?: number;
|
|
212
|
+
|
|
213
|
+
/** Callback when user logs in */
|
|
214
|
+
onLogin?: (user: User) => void;
|
|
215
|
+
|
|
216
|
+
/** Callback when user logs out */
|
|
217
|
+
onLogout?: () => void;
|
|
218
|
+
|
|
219
|
+
/** Callback when token is refreshed */
|
|
220
|
+
onTokenRefresh?: (tokens: TokenPair) => void;
|
|
221
|
+
|
|
222
|
+
/** Callback when auth error occurs */
|
|
223
|
+
onError?: (error: AuthError) => void;
|
|
224
|
+
|
|
225
|
+
/** Storage mechanism (default: localStorage) */
|
|
226
|
+
storage?: AuthStorage;
|
|
227
|
+
|
|
228
|
+
/** Enable debug logging (default: false) */
|
|
229
|
+
debug?: boolean;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface AuthStorage {
|
|
233
|
+
getItem: (key: string) => string | null | Promise<string | null>;
|
|
234
|
+
setItem: (key: string, value: string) => void | Promise<void>;
|
|
235
|
+
removeItem: (key: string) => void | Promise<void>;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// Context Types
|
|
240
|
+
// ============================================================================
|
|
241
|
+
|
|
242
|
+
export interface AuthContextValue extends AuthState {
|
|
243
|
+
// Auth actions
|
|
244
|
+
login: (credentials: LoginRequest) => Promise<LoginResponse>;
|
|
245
|
+
register: (data: RegisterRequest) => Promise<RegisterResponse>;
|
|
246
|
+
logout: () => Promise<void>;
|
|
247
|
+
verify: (data: VerifyRequest) => Promise<VerifyResponse>;
|
|
248
|
+
resendVerification: (data: ResendVerificationRequest) => Promise<ResendVerificationResponse>;
|
|
249
|
+
forgotPassword: (data: ForgotPasswordRequest) => Promise<ForgotPasswordResponse>;
|
|
250
|
+
recoverPassword: (data: RecoverPasswordRequest) => Promise<RecoverPasswordResponse>;
|
|
251
|
+
refreshTokens: () => Promise<void>;
|
|
252
|
+
|
|
253
|
+
// Utility
|
|
254
|
+
getAccessToken: () => string | null;
|
|
255
|
+
isAuthenticated: boolean;
|
|
256
|
+
isVerified: boolean;
|
|
257
|
+
hasRole: (role: UserRole | UserRole[]) => boolean;
|
|
258
|
+
|
|
259
|
+
// Config
|
|
260
|
+
config: AuthConfig;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ============================================================================
|
|
264
|
+
// Component Props Types
|
|
265
|
+
// ============================================================================
|
|
266
|
+
|
|
267
|
+
export interface LoginFormProps {
|
|
268
|
+
onSuccess?: (user: User) => void;
|
|
269
|
+
onError?: (error: AuthError) => void;
|
|
270
|
+
onNavigateRegister?: () => void;
|
|
271
|
+
onNavigateForgotPassword?: () => void;
|
|
272
|
+
showBranding?: boolean;
|
|
273
|
+
className?: string;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export interface RegisterFormProps {
|
|
277
|
+
onSuccess?: (user: User) => void;
|
|
278
|
+
onError?: (error: AuthError) => void;
|
|
279
|
+
onNavigateLogin?: () => void;
|
|
280
|
+
showBranding?: boolean;
|
|
281
|
+
className?: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export interface VerifyFormProps {
|
|
285
|
+
userName: string;
|
|
286
|
+
onSuccess?: (user: User) => void;
|
|
287
|
+
onError?: (error: AuthError) => void;
|
|
288
|
+
onNavigateLogin?: () => void;
|
|
289
|
+
showBranding?: boolean;
|
|
290
|
+
className?: string;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export interface ForgotPasswordFormProps {
|
|
294
|
+
onSuccess?: () => void;
|
|
295
|
+
onError?: (error: AuthError) => void;
|
|
296
|
+
onNavigateLogin?: () => void;
|
|
297
|
+
showBranding?: boolean;
|
|
298
|
+
className?: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export interface PixygonAuthProps {
|
|
302
|
+
mode: 'login' | 'register' | 'verify' | 'forgot-password' | 'recover-password';
|
|
303
|
+
onSuccess?: (user?: User) => void;
|
|
304
|
+
onError?: (error: AuthError) => void;
|
|
305
|
+
onModeChange?: (mode: string) => void;
|
|
306
|
+
userName?: string; // For verify mode
|
|
307
|
+
showBranding?: boolean;
|
|
308
|
+
theme?: 'dark' | 'light';
|
|
309
|
+
className?: string;
|
|
310
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pixygon/auth - Token Storage Utilities
|
|
3
|
+
* Unified storage for auth tokens across all Pixygon apps
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AuthStorage, User, TokenPair } from '../types';
|
|
7
|
+
|
|
8
|
+
// Storage keys - prefixed with app ID for isolation
|
|
9
|
+
const getKeys = (appId: string) => ({
|
|
10
|
+
ACCESS_TOKEN: `${appId}_access_token`,
|
|
11
|
+
REFRESH_TOKEN: `${appId}_refresh_token`,
|
|
12
|
+
USER: `${appId}_user`,
|
|
13
|
+
EXPIRES_AT: `${appId}_expires_at`,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Default storage using localStorage
|
|
17
|
+
const defaultStorage: AuthStorage = {
|
|
18
|
+
getItem: (key: string) => {
|
|
19
|
+
if (typeof window === 'undefined') return null;
|
|
20
|
+
return localStorage.getItem(key);
|
|
21
|
+
},
|
|
22
|
+
setItem: (key: string, value: string) => {
|
|
23
|
+
if (typeof window === 'undefined') return;
|
|
24
|
+
localStorage.setItem(key, value);
|
|
25
|
+
},
|
|
26
|
+
removeItem: (key: string) => {
|
|
27
|
+
if (typeof window === 'undefined') return;
|
|
28
|
+
localStorage.removeItem(key);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a token storage manager for a specific app
|
|
34
|
+
*/
|
|
35
|
+
export function createTokenStorage(appId: string, storage: AuthStorage = defaultStorage) {
|
|
36
|
+
const keys = getKeys(appId);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
/**
|
|
40
|
+
* Get the stored access token
|
|
41
|
+
*/
|
|
42
|
+
getAccessToken: async (): Promise<string | null> => {
|
|
43
|
+
const token = await storage.getItem(keys.ACCESS_TOKEN);
|
|
44
|
+
return token;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the stored refresh token
|
|
49
|
+
*/
|
|
50
|
+
getRefreshToken: async (): Promise<string | null> => {
|
|
51
|
+
const token = await storage.getItem(keys.REFRESH_TOKEN);
|
|
52
|
+
return token;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the stored user
|
|
57
|
+
*/
|
|
58
|
+
getUser: async (): Promise<User | null> => {
|
|
59
|
+
const userJson = await storage.getItem(keys.USER);
|
|
60
|
+
if (!userJson) return null;
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(userJson);
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get token expiration time
|
|
70
|
+
*/
|
|
71
|
+
getExpiresAt: async (): Promise<number | null> => {
|
|
72
|
+
const expiresAt = await storage.getItem(keys.EXPIRES_AT);
|
|
73
|
+
return expiresAt ? parseInt(expiresAt, 10) : null;
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if access token is expired
|
|
78
|
+
*/
|
|
79
|
+
isTokenExpired: async (): Promise<boolean> => {
|
|
80
|
+
const expiresAt = await storage.getItem(keys.EXPIRES_AT);
|
|
81
|
+
if (!expiresAt) return true;
|
|
82
|
+
return Date.now() >= parseInt(expiresAt, 10);
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if token will expire within threshold (in seconds)
|
|
87
|
+
*/
|
|
88
|
+
willExpireSoon: async (thresholdSeconds: number = 300): Promise<boolean> => {
|
|
89
|
+
const expiresAt = await storage.getItem(keys.EXPIRES_AT);
|
|
90
|
+
if (!expiresAt) return true;
|
|
91
|
+
const expiryTime = parseInt(expiresAt, 10);
|
|
92
|
+
const thresholdMs = thresholdSeconds * 1000;
|
|
93
|
+
return Date.now() >= expiryTime - thresholdMs;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Store tokens and user data
|
|
98
|
+
*/
|
|
99
|
+
setTokens: async (
|
|
100
|
+
accessToken: string,
|
|
101
|
+
refreshToken: string | null,
|
|
102
|
+
expiresIn: number | null,
|
|
103
|
+
user: User
|
|
104
|
+
): Promise<void> => {
|
|
105
|
+
await storage.setItem(keys.ACCESS_TOKEN, accessToken);
|
|
106
|
+
|
|
107
|
+
if (refreshToken) {
|
|
108
|
+
await storage.setItem(keys.REFRESH_TOKEN, refreshToken);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (expiresIn) {
|
|
112
|
+
const expiresAt = Date.now() + expiresIn * 1000;
|
|
113
|
+
await storage.setItem(keys.EXPIRES_AT, expiresAt.toString());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await storage.setItem(keys.USER, JSON.stringify(user));
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Update tokens after refresh
|
|
121
|
+
*/
|
|
122
|
+
updateTokens: async (tokens: TokenPair): Promise<void> => {
|
|
123
|
+
await storage.setItem(keys.ACCESS_TOKEN, tokens.accessToken);
|
|
124
|
+
await storage.setItem(keys.REFRESH_TOKEN, tokens.refreshToken);
|
|
125
|
+
const expiresAt = Date.now() + tokens.expiresIn * 1000;
|
|
126
|
+
await storage.setItem(keys.EXPIRES_AT, expiresAt.toString());
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Update user data
|
|
131
|
+
*/
|
|
132
|
+
updateUser: async (user: User): Promise<void> => {
|
|
133
|
+
await storage.setItem(keys.USER, JSON.stringify(user));
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Clear all stored auth data
|
|
138
|
+
*/
|
|
139
|
+
clear: async (): Promise<void> => {
|
|
140
|
+
await storage.removeItem(keys.ACCESS_TOKEN);
|
|
141
|
+
await storage.removeItem(keys.REFRESH_TOKEN);
|
|
142
|
+
await storage.removeItem(keys.USER);
|
|
143
|
+
await storage.removeItem(keys.EXPIRES_AT);
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get all stored auth data
|
|
148
|
+
*/
|
|
149
|
+
getAll: async () => {
|
|
150
|
+
const [accessToken, refreshToken, user, expiresAt] = await Promise.all([
|
|
151
|
+
storage.getItem(keys.ACCESS_TOKEN),
|
|
152
|
+
storage.getItem(keys.REFRESH_TOKEN),
|
|
153
|
+
storage.getItem(keys.USER),
|
|
154
|
+
storage.getItem(keys.EXPIRES_AT),
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
accessToken,
|
|
159
|
+
refreshToken,
|
|
160
|
+
user: user ? JSON.parse(user) : null,
|
|
161
|
+
expiresAt: expiresAt ? parseInt(expiresAt, 10) : null,
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export type TokenStorage = ReturnType<typeof createTokenStorage>;
|