@oxyhq/services 5.8.1 → 5.8.3
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/lib/commonjs/index.js +9 -27
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/node/createAuth.js +7 -585
- package/lib/commonjs/node/createAuth.js.map +1 -1
- package/lib/commonjs/node/index.js +1 -38
- package/lib/commonjs/node/index.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +100 -12
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/Header.js +40 -6
- package/lib/commonjs/ui/components/Header.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +5 -0
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +63 -125
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/index.js +6 -0
- package/lib/commonjs/ui/hooks/index.js.map +1 -1
- package/lib/commonjs/ui/hooks/useFollow.js +59 -2
- package/lib/commonjs/ui/hooks/useFollow.js.map +1 -1
- package/lib/commonjs/ui/navigation/OxyRouter.js +10 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +9 -0
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +214 -37
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/UserLinksScreen.js +90 -0
- package/lib/commonjs/ui/screens/UserLinksScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js +9 -6
- package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +3 -30
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js +37 -46
- package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +9 -12
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js +9 -12
- package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +9 -12
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
- package/lib/commonjs/ui/stores/authStore.js +24 -6
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/commonjs/ui/stores/followStore.js +106 -1
- package/lib/commonjs/ui/stores/followStore.js.map +1 -1
- package/lib/module/index.js +1 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/node/createAuth.js +7 -584
- package/lib/module/node/createAuth.js.map +1 -1
- package/lib/module/node/index.js +1 -7
- package/lib/module/node/index.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +101 -13
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/Header.js +40 -6
- package/lib/module/ui/components/Header.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +5 -0
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +63 -125
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/index.js +1 -1
- package/lib/module/ui/hooks/index.js.map +1 -1
- package/lib/module/ui/hooks/useFollow.js +57 -1
- package/lib/module/ui/hooks/useFollow.js.map +1 -1
- package/lib/module/ui/navigation/OxyRouter.js +10 -0
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +9 -0
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +214 -37
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/UserLinksScreen.js +85 -0
- package/lib/module/ui/screens/UserLinksScreen.js.map +1 -0
- package/lib/module/ui/screens/karma/KarmaAboutScreen.js +9 -6
- package/lib/module/ui/screens/karma/KarmaAboutScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js +3 -30
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaFAQScreen.js +37 -46
- package/lib/module/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +9 -12
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaRewardsScreen.js +9 -12
- package/lib/module/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js +9 -12
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
- package/lib/module/ui/stores/authStore.js +24 -6
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/module/ui/stores/followStore.js +106 -1
- package/lib/module/ui/stores/followStore.js.map +1 -1
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/node/createAuth.d.ts +0 -112
- package/lib/typescript/node/createAuth.d.ts.map +1 -1
- package/lib/typescript/node/index.d.ts +0 -2
- package/lib/typescript/node/index.d.ts.map +1 -1
- package/lib/typescript/ui/components/FollowButton.d.ts +1 -0
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/Header.d.ts +2 -0
- package/lib/typescript/ui/components/Header.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/index.d.ts +1 -1
- package/lib/typescript/ui/hooks/index.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useFollow.d.ts +20 -0
- package/lib/typescript/ui/hooks/useFollow.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/UserLinksScreen.d.ts +15 -0
- package/lib/typescript/ui/screens/UserLinksScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/karma/KarmaAboutScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaCenterScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaRulesScreen.d.ts.map +1 -1
- package/lib/typescript/ui/stores/authStore.d.ts +3 -1
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/ui/stores/followStore.d.ts +10 -0
- package/lib/typescript/ui/stores/followStore.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +2 -10
- package/src/node/createAuth.ts +7 -623
- package/src/node/index.ts +1 -19
- package/src/ui/components/FollowButton.tsx +95 -11
- package/src/ui/components/Header.tsx +45 -4
- package/src/ui/components/OxyProvider.tsx +6 -0
- package/src/ui/context/OxyContext.tsx +65 -136
- package/src/ui/hooks/index.ts +1 -1
- package/src/ui/hooks/useFollow.ts +63 -0
- package/src/ui/navigation/OxyRouter.tsx +10 -0
- package/src/ui/screens/AccountSettingsScreen.tsx +8 -0
- package/src/ui/screens/ProfileScreen.tsx +191 -28
- package/src/ui/screens/UserLinksScreen.tsx +96 -0
- package/src/ui/screens/karma/KarmaAboutScreen.tsx +9 -2
- package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -20
- package/src/ui/screens/karma/KarmaFAQScreen.tsx +40 -24
- package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +9 -3
- package/src/ui/screens/karma/KarmaRewardsScreen.tsx +9 -3
- package/src/ui/screens/karma/KarmaRulesScreen.tsx +9 -3
- package/src/ui/stores/authStore.ts +22 -7
- package/src/ui/stores/followStore.ts +102 -1
package/src/node/createAuth.ts
CHANGED
|
@@ -1,463 +1,13 @@
|
|
|
1
|
-
import express
|
|
2
|
-
import type {
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import type { Request, Response } from 'express';
|
|
3
3
|
import { OxyServices } from '../core';
|
|
4
|
-
import { jwtDecode } from 'jwt-decode';
|
|
5
|
-
|
|
6
|
-
// Types for enhanced authentication
|
|
7
|
-
export interface AuthRequest extends Request {
|
|
8
|
-
user?: any;
|
|
9
|
-
userId?: string;
|
|
10
|
-
accessToken?: string;
|
|
11
|
-
sessionId?: string;
|
|
12
|
-
deviceFingerprint?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface AuthOptions {
|
|
16
|
-
baseURL: string;
|
|
17
|
-
jwtSecret?: string; // For local JWT validation
|
|
18
|
-
loadFullUser?: boolean;
|
|
19
|
-
enableSessionAuth?: boolean;
|
|
20
|
-
enableDeviceAuth?: boolean;
|
|
21
|
-
cacheUserData?: boolean;
|
|
22
|
-
userCacheTTL?: number; // in seconds
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface AuthMiddlewareOptions {
|
|
26
|
-
required?: boolean;
|
|
27
|
-
loadFullUser?: boolean;
|
|
28
|
-
roles?: string[];
|
|
29
|
-
permissions?: string[];
|
|
30
|
-
onError?: (error: any, req: AuthRequest, res: Response) => void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface TokenValidationResult {
|
|
34
|
-
valid: boolean;
|
|
35
|
-
userId?: string;
|
|
36
|
-
user?: any;
|
|
37
|
-
error?: string;
|
|
38
|
-
code?: string;
|
|
39
|
-
expiresAt?: number;
|
|
40
|
-
cached?: boolean;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// User cache for performance
|
|
44
|
-
class UserCache {
|
|
45
|
-
private cache = new Map<string, { user: any; expiresAt: number }>();
|
|
46
|
-
private ttl: number;
|
|
47
|
-
|
|
48
|
-
constructor(ttl: number = 300) { // 5 minutes default
|
|
49
|
-
this.ttl = ttl * 1000;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
set(userId: string, user: any): void {
|
|
53
|
-
this.cache.set(userId, {
|
|
54
|
-
user,
|
|
55
|
-
expiresAt: Date.now() + this.ttl
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
get(userId: string): any | null {
|
|
60
|
-
const item = this.cache.get(userId);
|
|
61
|
-
if (!item || Date.now() > item.expiresAt) {
|
|
62
|
-
this.cache.delete(userId);
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
return item.user;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
clear(): void {
|
|
69
|
-
this.cache.clear();
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Enhanced OxyAuth class for backend authentication
|
|
75
|
-
*/
|
|
76
|
-
export class OxyAuth {
|
|
77
|
-
private oxy: OxyServices;
|
|
78
|
-
private options: AuthOptions;
|
|
79
|
-
private userCache: UserCache | null = null;
|
|
80
|
-
|
|
81
|
-
constructor(options: AuthOptions) {
|
|
82
|
-
this.options = {
|
|
83
|
-
loadFullUser: true,
|
|
84
|
-
enableSessionAuth: true,
|
|
85
|
-
enableDeviceAuth: true,
|
|
86
|
-
cacheUserData: true,
|
|
87
|
-
userCacheTTL: 300,
|
|
88
|
-
...options
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
this.oxy = new OxyServices({ baseURL: options.baseURL });
|
|
92
|
-
|
|
93
|
-
if (this.options.cacheUserData) {
|
|
94
|
-
this.userCache = new UserCache(this.options.userCacheTTL);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Create authentication middleware
|
|
100
|
-
*/
|
|
101
|
-
createAuthMiddleware(options: AuthMiddlewareOptions = {}): (req: AuthRequest, res: Response, next: NextFunction) => Promise<void> {
|
|
102
|
-
return async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
103
|
-
try {
|
|
104
|
-
const result = await this.authenticateRequest(req);
|
|
105
|
-
|
|
106
|
-
if (!result.valid && options.required !== false) {
|
|
107
|
-
const error = { message: 'Authentication required', code: 'AUTH_REQUIRED' };
|
|
108
|
-
if (options.onError) {
|
|
109
|
-
options.onError(error, req, res);
|
|
110
|
-
} else {
|
|
111
|
-
res.status(401).json(error);
|
|
112
|
-
}
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Check roles if specified
|
|
117
|
-
if (result.valid && options.roles && result.user) {
|
|
118
|
-
const hasRole = options.roles.some(role =>
|
|
119
|
-
result.user.roles?.includes(role) || result.user.role === role
|
|
120
|
-
);
|
|
121
|
-
if (!hasRole) {
|
|
122
|
-
const error = { message: 'Insufficient permissions', code: 'INSUFFICIENT_ROLES' };
|
|
123
|
-
if (options.onError) {
|
|
124
|
-
options.onError(error, req, res);
|
|
125
|
-
} else {
|
|
126
|
-
res.status(403).json(error);
|
|
127
|
-
}
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Check permissions if specified
|
|
133
|
-
if (result.valid && options.permissions && result.userId) {
|
|
134
|
-
for (const permission of options.permissions) {
|
|
135
|
-
const hasPermission = await this.hasPermission(result.userId!, permission);
|
|
136
|
-
if (!hasPermission) {
|
|
137
|
-
const error = { message: 'Insufficient permissions', code: 'INSUFFICIENT_PERMISSIONS' };
|
|
138
|
-
if (options.onError) {
|
|
139
|
-
options.onError(error, req, res);
|
|
140
|
-
} else {
|
|
141
|
-
res.status(403).json(error);
|
|
142
|
-
}
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
next();
|
|
149
|
-
} catch (error) {
|
|
150
|
-
if (options.onError) {
|
|
151
|
-
options.onError(error, req, res);
|
|
152
|
-
} else {
|
|
153
|
-
res.status(500).json({ message: 'Authentication error' });
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Authenticate request and populate user data
|
|
161
|
-
*/
|
|
162
|
-
private async authenticateRequest(req: AuthRequest): Promise<TokenValidationResult & { accessToken?: string }> {
|
|
163
|
-
// Try JWT token first
|
|
164
|
-
const authHeader = req.headers.authorization;
|
|
165
|
-
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
166
|
-
const token = authHeader.substring(7);
|
|
167
|
-
const result = await this.validateToken(token);
|
|
168
|
-
if (result.valid) {
|
|
169
|
-
req.user = result.user;
|
|
170
|
-
req.userId = result.userId;
|
|
171
|
-
req.accessToken = token;
|
|
172
|
-
return { ...result, accessToken: token };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Try session-based auth
|
|
177
|
-
if (this.options.enableSessionAuth) {
|
|
178
|
-
const sessionId = req.headers['x-session-id'] as string;
|
|
179
|
-
if (sessionId) {
|
|
180
|
-
const result = await this.validateSession(sessionId);
|
|
181
|
-
if (result.valid) {
|
|
182
|
-
req.user = result.user;
|
|
183
|
-
req.userId = result.userId;
|
|
184
|
-
req.sessionId = sessionId;
|
|
185
|
-
return result;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Try device-based auth
|
|
191
|
-
if (this.options.enableDeviceAuth) {
|
|
192
|
-
const deviceFingerprint = req.headers['x-device-fingerprint'] as string;
|
|
193
|
-
const userId = req.headers['x-user-id'] as string;
|
|
194
|
-
if (deviceFingerprint && userId) {
|
|
195
|
-
const result = await this.validateDevice(userId, deviceFingerprint);
|
|
196
|
-
if (result.valid) {
|
|
197
|
-
req.user = result.user;
|
|
198
|
-
req.userId = result.userId;
|
|
199
|
-
req.deviceFingerprint = deviceFingerprint;
|
|
200
|
-
return result;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return { valid: false, error: 'No valid authentication found' };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Validate JWT token
|
|
210
|
-
*/
|
|
211
|
-
async validateToken(token: string): Promise<TokenValidationResult> {
|
|
212
|
-
try {
|
|
213
|
-
// Local JWT validation if secret is provided
|
|
214
|
-
if (this.options.jwtSecret) {
|
|
215
|
-
const decoded = jwtDecode(token) as any;
|
|
216
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
|
217
|
-
|
|
218
|
-
if (decoded.exp && decoded.exp < currentTime) {
|
|
219
|
-
return {
|
|
220
|
-
valid: false,
|
|
221
|
-
error: 'Token expired',
|
|
222
|
-
code: 'TOKEN_EXPIRED',
|
|
223
|
-
expiresAt: decoded.exp
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const userId = decoded.userId || decoded.id;
|
|
228
|
-
if (!userId) {
|
|
229
|
-
return {
|
|
230
|
-
valid: false,
|
|
231
|
-
error: 'Invalid token payload',
|
|
232
|
-
code: 'INVALID_PAYLOAD'
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Get user data from cache or API
|
|
237
|
-
let user = this.userCache?.get(userId);
|
|
238
|
-
const cached = !!user;
|
|
239
|
-
|
|
240
|
-
if (!user && this.options.loadFullUser) {
|
|
241
|
-
try {
|
|
242
|
-
user = await this.oxy.getUserById(userId);
|
|
243
|
-
this.userCache?.set(userId, user);
|
|
244
|
-
} catch (error) {
|
|
245
|
-
user = { id: userId };
|
|
246
|
-
}
|
|
247
|
-
} else if (!user) {
|
|
248
|
-
user = { id: userId };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
valid: true,
|
|
253
|
-
userId,
|
|
254
|
-
user,
|
|
255
|
-
expiresAt: decoded.exp,
|
|
256
|
-
cached
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Remote validation using OxyServices
|
|
261
|
-
const tempOxy = new OxyServices({ baseURL: this.oxy.getBaseURL() });
|
|
262
|
-
tempOxy.setTokens(token, '');
|
|
263
|
-
|
|
264
|
-
const isValid = await tempOxy.validate();
|
|
265
|
-
if (!isValid) {
|
|
266
|
-
return {
|
|
267
|
-
valid: false,
|
|
268
|
-
error: 'Invalid token',
|
|
269
|
-
code: 'INVALID_TOKEN'
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const userId = tempOxy.getCurrentUserId();
|
|
274
|
-
if (!userId) {
|
|
275
|
-
return {
|
|
276
|
-
valid: false,
|
|
277
|
-
error: 'Invalid token payload',
|
|
278
|
-
code: 'INVALID_PAYLOAD'
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Get user data
|
|
283
|
-
let user = this.userCache?.get(userId);
|
|
284
|
-
const cached = !!user;
|
|
285
|
-
|
|
286
|
-
if (!user && this.options.loadFullUser) {
|
|
287
|
-
try {
|
|
288
|
-
user = await tempOxy.getUserById(userId);
|
|
289
|
-
this.userCache?.set(userId, user);
|
|
290
|
-
} catch (error) {
|
|
291
|
-
user = { id: userId };
|
|
292
|
-
}
|
|
293
|
-
} else if (!user) {
|
|
294
|
-
user = { id: userId };
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return {
|
|
298
|
-
valid: true,
|
|
299
|
-
userId,
|
|
300
|
-
user,
|
|
301
|
-
cached
|
|
302
|
-
};
|
|
303
|
-
} catch (error) {
|
|
304
|
-
return {
|
|
305
|
-
valid: false,
|
|
306
|
-
error: 'Token validation failed',
|
|
307
|
-
code: 'VALIDATION_ERROR'
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Validate session-based authentication
|
|
314
|
-
*/
|
|
315
|
-
async validateSession(sessionId: string, deviceFingerprint?: string): Promise<TokenValidationResult> {
|
|
316
|
-
try {
|
|
317
|
-
// This would integrate with your session management system
|
|
318
|
-
// For now, it's a placeholder implementation
|
|
319
|
-
return {
|
|
320
|
-
valid: false,
|
|
321
|
-
error: 'Session validation not implemented',
|
|
322
|
-
code: 'NOT_IMPLEMENTED'
|
|
323
|
-
};
|
|
324
|
-
} catch (error) {
|
|
325
|
-
return {
|
|
326
|
-
valid: false,
|
|
327
|
-
error: 'Session validation failed',
|
|
328
|
-
code: 'VALIDATION_ERROR'
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Validate device-based authentication
|
|
335
|
-
*/
|
|
336
|
-
async validateDevice(userId: string, deviceFingerprint: string): Promise<TokenValidationResult> {
|
|
337
|
-
try {
|
|
338
|
-
// This would validate device fingerprint against stored data
|
|
339
|
-
// For now, it's a placeholder implementation
|
|
340
|
-
return {
|
|
341
|
-
valid: false,
|
|
342
|
-
error: 'Device validation not implemented',
|
|
343
|
-
code: 'NOT_IMPLEMENTED'
|
|
344
|
-
};
|
|
345
|
-
} catch (error) {
|
|
346
|
-
return {
|
|
347
|
-
valid: false,
|
|
348
|
-
error: 'Device validation failed',
|
|
349
|
-
code: 'VALIDATION_ERROR'
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Create role-based middleware
|
|
356
|
-
*/
|
|
357
|
-
requireRole(roles: string | string[]): (req: AuthRequest, res: Response, next: NextFunction) => Promise<void> {
|
|
358
|
-
const roleArray = Array.isArray(roles) ? roles : [roles];
|
|
359
|
-
return this.createAuthMiddleware({
|
|
360
|
-
required: true,
|
|
361
|
-
roles: roleArray
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Create permission-based middleware
|
|
367
|
-
*/
|
|
368
|
-
requirePermission(permissions: string | string[]): (req: AuthRequest, res: Response, next: NextFunction) => Promise<void> {
|
|
369
|
-
const permissionArray = Array.isArray(permissions) ? permissions : [permissions];
|
|
370
|
-
return this.createAuthMiddleware({
|
|
371
|
-
required: true,
|
|
372
|
-
permissions: permissionArray
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Create optional authentication middleware
|
|
378
|
-
*/
|
|
379
|
-
optionalAuth(): (req: AuthRequest, res: Response, next: NextFunction) => Promise<void> {
|
|
380
|
-
return this.createAuthMiddleware({
|
|
381
|
-
required: false,
|
|
382
|
-
onError: () => {} // No error thrown for optional auth
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Clear user cache
|
|
388
|
-
*/
|
|
389
|
-
clearCache(): void {
|
|
390
|
-
this.userCache?.clear();
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Check if user data is cached for a given token
|
|
395
|
-
*/
|
|
396
|
-
isUserCached(token: string): boolean {
|
|
397
|
-
try {
|
|
398
|
-
const decoded = jwtDecode(token) as any;
|
|
399
|
-
const userId = decoded.userId || decoded.id;
|
|
400
|
-
return userId ? this.userCache?.get(userId) !== null : false;
|
|
401
|
-
} catch {
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Check if user has a specific permission
|
|
408
|
-
*/
|
|
409
|
-
async hasPermission(userId: string, permission: string): Promise<boolean> {
|
|
410
|
-
try {
|
|
411
|
-
// This is a placeholder implementation
|
|
412
|
-
// In a real implementation, you would check against user roles/permissions
|
|
413
|
-
const user = this.userCache?.get(userId) || await this.oxy.getUserById(userId);
|
|
414
|
-
return user?.permissions?.includes(permission) || user?.role === 'admin' || false;
|
|
415
|
-
} catch {
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Get OxyServices instance
|
|
422
|
-
*/
|
|
423
|
-
getOxyServices(): OxyServices {
|
|
424
|
-
return this.oxy;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
4
|
|
|
428
5
|
export interface CreateAuthOptions {
|
|
429
6
|
baseURL: string;
|
|
430
|
-
jwtSecret?: string;
|
|
431
|
-
loadFullUser?: boolean;
|
|
432
|
-
enableSessionAuth?: boolean;
|
|
433
|
-
enableDeviceAuth?: boolean;
|
|
434
|
-
cacheUserData?: boolean;
|
|
435
|
-
userCacheTTL?: number;
|
|
436
7
|
}
|
|
437
8
|
|
|
438
|
-
/**
|
|
439
|
-
* Enhanced createAuth function that provides both router and middleware capabilities
|
|
440
|
-
*
|
|
441
|
-
* This is a unified authentication system that:
|
|
442
|
-
* 1. Maintains backward compatibility with the old router-based approach
|
|
443
|
-
* 2. Adds powerful new middleware capabilities
|
|
444
|
-
* 3. Includes caching, role-based access, and performance optimizations
|
|
445
|
-
* 4. Supports multiple authentication strategies
|
|
446
|
-
*/
|
|
447
9
|
export function createAuth(options: CreateAuthOptions) {
|
|
448
|
-
|
|
449
|
-
const authOptions: AuthOptions = {
|
|
450
|
-
baseURL: options.baseURL,
|
|
451
|
-
jwtSecret: options.jwtSecret,
|
|
452
|
-
loadFullUser: options.loadFullUser ?? true,
|
|
453
|
-
enableSessionAuth: options.enableSessionAuth ?? true,
|
|
454
|
-
enableDeviceAuth: options.enableDeviceAuth ?? true,
|
|
455
|
-
cacheUserData: options.cacheUserData ?? true,
|
|
456
|
-
userCacheTTL: options.userCacheTTL ?? 300
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
const oxyAuth = new OxyAuth(authOptions);
|
|
460
|
-
const oxy = oxyAuth.getOxyServices();
|
|
10
|
+
const oxy = new OxyServices({ baseURL: options.baseURL });
|
|
461
11
|
const router = express.Router();
|
|
462
12
|
|
|
463
13
|
// Helper to handle async route functions
|
|
@@ -472,261 +22,95 @@ export function createAuth(options: CreateAuthOptions) {
|
|
|
472
22
|
}
|
|
473
23
|
};
|
|
474
24
|
|
|
475
|
-
// Enhanced signup with validation
|
|
476
25
|
router.post(
|
|
477
26
|
'/signup',
|
|
478
27
|
wrap(async (req, res) => {
|
|
479
28
|
const { username, email, password } = req.body;
|
|
480
|
-
|
|
481
|
-
// Enhanced validation
|
|
482
|
-
if (!username || !email || !password) {
|
|
483
|
-
return res.status(400).json({
|
|
484
|
-
message: 'Username, email, and password are required'
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
|
|
488
29
|
const result = await oxy.signUp(username, email, password);
|
|
489
30
|
res.json(result);
|
|
490
31
|
})
|
|
491
32
|
);
|
|
492
33
|
|
|
493
|
-
// Enhanced login with device fingerprinting
|
|
494
34
|
router.post(
|
|
495
35
|
'/login',
|
|
496
36
|
wrap(async (req, res) => {
|
|
497
|
-
const { username, password
|
|
498
|
-
|
|
499
|
-
if (!username || !password) {
|
|
500
|
-
return res.status(400).json({
|
|
501
|
-
message: 'Username and password are required'
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
|
|
37
|
+
const { username, password } = req.body;
|
|
505
38
|
const result = await oxy.login(username, password);
|
|
506
|
-
|
|
507
|
-
// Store device fingerprint if provided
|
|
508
|
-
if (deviceFingerprint && result.user?.id) {
|
|
509
|
-
// This could be stored in a database for device tracking
|
|
510
|
-
console.log(`Device login: ${deviceFingerprint} for user ${result.user.id}`);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
39
|
res.json(result);
|
|
514
40
|
})
|
|
515
41
|
);
|
|
516
42
|
|
|
517
|
-
// Enhanced logout with session management
|
|
518
43
|
router.post(
|
|
519
44
|
'/logout',
|
|
520
45
|
wrap(async (req, res) => {
|
|
521
46
|
const token = req.headers.authorization?.split(' ')[1];
|
|
522
47
|
const refreshToken = req.body.refreshToken;
|
|
523
|
-
const sessionId = req.body.sessionId;
|
|
524
|
-
|
|
525
48
|
if (token) oxy.setTokens(token, refreshToken);
|
|
526
|
-
|
|
527
|
-
// Enhanced logout with session tracking
|
|
528
|
-
if (sessionId) {
|
|
529
|
-
await oxy.logoutSession(sessionId);
|
|
530
|
-
} else {
|
|
531
|
-
await oxy.logout();
|
|
532
|
-
}
|
|
533
|
-
|
|
49
|
+
await oxy.logout();
|
|
534
50
|
res.json({ success: true });
|
|
535
51
|
})
|
|
536
52
|
);
|
|
537
53
|
|
|
538
|
-
// Enhanced token refresh
|
|
539
54
|
router.post(
|
|
540
55
|
'/refresh',
|
|
541
56
|
wrap(async (req, res) => {
|
|
542
57
|
const refreshToken = req.body.refreshToken;
|
|
543
58
|
const accessToken = req.headers.authorization?.split(' ')[1] || '';
|
|
544
|
-
|
|
545
|
-
if (!refreshToken) {
|
|
546
|
-
return res.status(400).json({ message: 'Refresh token is required' });
|
|
547
|
-
}
|
|
548
|
-
|
|
549
59
|
oxy.setTokens(accessToken, refreshToken);
|
|
550
60
|
const tokens = await oxy.refreshTokens();
|
|
551
61
|
res.json(tokens);
|
|
552
62
|
})
|
|
553
63
|
);
|
|
554
64
|
|
|
555
|
-
// Enhanced token validation with caching
|
|
556
65
|
router.get(
|
|
557
66
|
'/validate',
|
|
558
67
|
wrap(async (req, res) => {
|
|
559
68
|
const token = req.headers.authorization?.split(' ')[1] || '';
|
|
560
|
-
|
|
561
|
-
if (!token) {
|
|
562
|
-
return res.status(401).json({ valid: false, message: 'No token provided' });
|
|
563
|
-
}
|
|
564
|
-
|
|
565
69
|
oxy.setTokens(token, '');
|
|
566
70
|
const valid = await oxy.validate();
|
|
567
|
-
|
|
568
|
-
// Enhanced response with more details
|
|
569
|
-
res.json({
|
|
570
|
-
valid,
|
|
571
|
-
timestamp: new Date().toISOString(),
|
|
572
|
-
cached: oxyAuth.isUserCached(token) // Check if user data is cached
|
|
573
|
-
});
|
|
71
|
+
res.json({ valid });
|
|
574
72
|
})
|
|
575
73
|
);
|
|
576
74
|
|
|
577
|
-
// Enhanced sessions management
|
|
578
75
|
router.get(
|
|
579
76
|
'/sessions',
|
|
580
77
|
wrap(async (req, res) => {
|
|
581
78
|
const token = req.headers.authorization?.split(' ')[1] || '';
|
|
582
|
-
|
|
583
|
-
if (!token) {
|
|
584
|
-
return res.status(401).json({ message: 'Authentication required' });
|
|
585
|
-
}
|
|
586
|
-
|
|
587
79
|
oxy.setTokens(token, '');
|
|
588
80
|
const sessions = await oxy.getUserSessions();
|
|
589
81
|
res.json(sessions);
|
|
590
82
|
})
|
|
591
83
|
);
|
|
592
84
|
|
|
593
|
-
// Enhanced session deletion
|
|
594
85
|
router.delete(
|
|
595
86
|
'/sessions/:id',
|
|
596
87
|
wrap(async (req, res) => {
|
|
597
88
|
const token = req.headers.authorization?.split(' ')[1] || '';
|
|
598
|
-
|
|
599
|
-
if (!token) {
|
|
600
|
-
return res.status(401).json({ message: 'Authentication required' });
|
|
601
|
-
}
|
|
602
|
-
|
|
603
89
|
oxy.setTokens(token, '');
|
|
604
90
|
const result = await oxy.logoutSession(req.params.id);
|
|
605
|
-
|
|
606
|
-
// Clear cache for this user if logout was successful
|
|
607
|
-
if (result.success) {
|
|
608
|
-
oxyAuth.clearCache();
|
|
609
|
-
}
|
|
610
|
-
|
|
611
91
|
res.json(result);
|
|
612
92
|
})
|
|
613
93
|
);
|
|
614
94
|
|
|
615
|
-
// Enhanced logout other sessions
|
|
616
95
|
router.post(
|
|
617
96
|
'/sessions/logout-others',
|
|
618
97
|
wrap(async (req, res) => {
|
|
619
98
|
const token = req.headers.authorization?.split(' ')[1] || '';
|
|
620
|
-
|
|
621
|
-
if (!token) {
|
|
622
|
-
return res.status(401).json({ message: 'Authentication required' });
|
|
623
|
-
}
|
|
624
|
-
|
|
625
99
|
oxy.setTokens(token, '');
|
|
626
100
|
const result = await oxy.logoutOtherSessions();
|
|
627
|
-
|
|
628
|
-
// Clear cache for this user
|
|
629
|
-
if (result.success) {
|
|
630
|
-
oxyAuth.clearCache();
|
|
631
|
-
}
|
|
632
|
-
|
|
633
101
|
res.json(result);
|
|
634
102
|
})
|
|
635
103
|
);
|
|
636
104
|
|
|
637
|
-
// Enhanced logout all sessions
|
|
638
105
|
router.post(
|
|
639
106
|
'/sessions/logout-all',
|
|
640
107
|
wrap(async (req, res) => {
|
|
641
108
|
const token = req.headers.authorization?.split(' ')[1] || '';
|
|
642
|
-
|
|
643
|
-
if (!token) {
|
|
644
|
-
return res.status(401).json({ message: 'Authentication required' });
|
|
645
|
-
}
|
|
646
|
-
|
|
647
109
|
oxy.setTokens(token, '');
|
|
648
110
|
const result = await oxy.logoutAllSessions();
|
|
649
|
-
|
|
650
|
-
// Clear all cache
|
|
651
|
-
if (result.success) {
|
|
652
|
-
oxyAuth.clearCache();
|
|
653
|
-
}
|
|
654
|
-
|
|
655
111
|
res.json(result);
|
|
656
112
|
})
|
|
657
113
|
);
|
|
658
114
|
|
|
659
|
-
|
|
660
|
-
router.get(
|
|
661
|
-
'/profile',
|
|
662
|
-
wrap(async (req, res) => {
|
|
663
|
-
const token = req.headers.authorization?.split(' ')[1] || '';
|
|
664
|
-
|
|
665
|
-
if (!token) {
|
|
666
|
-
return res.status(401).json({ message: 'Authentication required' });
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// Use the enhanced auth system for better performance
|
|
670
|
-
const validation = await oxyAuth.validateToken(token);
|
|
671
|
-
|
|
672
|
-
if (!validation.valid) {
|
|
673
|
-
return res.status(401).json({ message: 'Invalid token' });
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
res.json({
|
|
677
|
-
user: validation.user,
|
|
678
|
-
cached: validation.cached,
|
|
679
|
-
expiresAt: validation.expiresAt
|
|
680
|
-
});
|
|
681
|
-
})
|
|
682
|
-
);
|
|
683
|
-
|
|
684
|
-
// NEW: Check user permissions
|
|
685
|
-
router.post(
|
|
686
|
-
'/check-permissions',
|
|
687
|
-
wrap(async (req, res) => {
|
|
688
|
-
const token = req.headers.authorization?.split(' ')[1] || '';
|
|
689
|
-
const { permissions } = req.body;
|
|
690
|
-
|
|
691
|
-
if (!token) {
|
|
692
|
-
return res.status(401).json({ message: 'Authentication required' });
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
if (!permissions || !Array.isArray(permissions)) {
|
|
696
|
-
return res.status(400).json({ message: 'Permissions array is required' });
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const validation = await oxyAuth.validateToken(token);
|
|
700
|
-
|
|
701
|
-
if (!validation.valid) {
|
|
702
|
-
return res.status(401).json({ message: 'Invalid token' });
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// Check each permission
|
|
706
|
-
const results = await Promise.all(
|
|
707
|
-
permissions.map(async (permission) => {
|
|
708
|
-
const hasPermission = await oxyAuth.hasPermission(validation.userId!, permission);
|
|
709
|
-
return { permission, granted: hasPermission };
|
|
710
|
-
})
|
|
711
|
-
);
|
|
712
|
-
|
|
713
|
-
res.json({ permissions: results });
|
|
714
|
-
})
|
|
715
|
-
);
|
|
716
|
-
|
|
717
|
-
return {
|
|
718
|
-
middleware: router,
|
|
719
|
-
// NEW: Expose the enhanced auth system
|
|
720
|
-
auth: oxyAuth,
|
|
721
|
-
// NEW: Convenience methods for middleware
|
|
722
|
-
requireAuth: (roles?: string | string[], permissions?: string | string[]) =>
|
|
723
|
-
oxyAuth.createAuthMiddleware({
|
|
724
|
-
required: true,
|
|
725
|
-
roles: Array.isArray(roles) ? roles : roles ? [roles] : undefined,
|
|
726
|
-
permissions: Array.isArray(permissions) ? permissions : permissions ? [permissions] : undefined
|
|
727
|
-
}),
|
|
728
|
-
optionalAuth: () => oxyAuth.optionalAuth(),
|
|
729
|
-
requireRole: (roles: string | string[]) => oxyAuth.requireRole(roles),
|
|
730
|
-
requirePermission: (permissions: string | string[]) => oxyAuth.requirePermission(permissions)
|
|
731
|
-
};
|
|
115
|
+
return { middleware: router };
|
|
732
116
|
}
|