bxo 0.0.5-dev.8 → 0.0.5-dev.81

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/plugins/auth.ts DELETED
@@ -1,119 +0,0 @@
1
- import BXO from '../index';
2
-
3
- interface AuthOptions {
4
- type: 'jwt' | 'bearer' | 'apikey';
5
- secret?: string;
6
- header?: string;
7
- verify?: (token: string, ctx: any) => Promise<any> | any;
8
- exclude?: string[];
9
- }
10
-
11
- export function auth(options: AuthOptions): BXO {
12
- const {
13
- type,
14
- secret,
15
- header = 'authorization',
16
- verify,
17
- exclude = []
18
- } = options;
19
-
20
- const authInstance = new BXO();
21
-
22
- authInstance.onRequest(async (ctx: any) => {
23
- const url = new URL(ctx.request.url);
24
- const pathname = url.pathname;
25
-
26
- // Skip auth for excluded paths
27
- if (exclude.some(path => {
28
- if (path.includes('*')) {
29
- const regex = new RegExp(path.replace(/\*/g, '.*'));
30
- return regex.test(pathname);
31
- }
32
- return pathname === path || pathname.startsWith(path);
33
- })) {
34
- return;
35
- }
36
-
37
- const authHeader = ctx.request.headers.get(header.toLowerCase());
38
-
39
- if (!authHeader) {
40
- throw new Response(JSON.stringify({ error: 'Authorization header required' }), {
41
- status: 401,
42
- headers: { 'Content-Type': 'application/json' }
43
- });
44
- }
45
-
46
- let token: string;
47
-
48
- if (type === 'jwt' || type === 'bearer') {
49
- if (!authHeader.startsWith('Bearer ')) {
50
- throw new Response(JSON.stringify({ error: 'Invalid authorization format. Use Bearer <token>' }), {
51
- status: 401,
52
- headers: { 'Content-Type': 'application/json' }
53
- });
54
- }
55
- token = authHeader.slice(7);
56
- } else if (type === 'apikey') {
57
- token = authHeader;
58
- } else {
59
- token = authHeader;
60
- }
61
-
62
- try {
63
- let user: any;
64
-
65
- if (verify) {
66
- user = await verify(token, ctx);
67
- } else if (type === 'jwt' && secret) {
68
- // Simple JWT verification (in production, use a proper JWT library)
69
- const [headerB64, payloadB64, signature] = token.split('.');
70
- if (!headerB64 || !payloadB64 || !signature) {
71
- throw new Error('Invalid JWT format');
72
- }
73
-
74
- const payload = JSON.parse(atob(payloadB64));
75
-
76
- // Check expiration
77
- if (payload.exp && Date.now() >= payload.exp * 1000) {
78
- throw new Error('Token expired');
79
- }
80
-
81
- user = payload;
82
- } else {
83
- user = { token };
84
- }
85
-
86
- // Attach user to context
87
- ctx.user = user;
88
-
89
- } catch (error) {
90
- const message = error instanceof Error ? error.message : 'Invalid token';
91
- throw new Response(JSON.stringify({ error: message }), {
92
- status: 401,
93
- headers: { 'Content-Type': 'application/json' }
94
- });
95
- }
96
- });
97
-
98
- return authInstance;
99
- }
100
-
101
- // Helper function for creating JWT tokens (simple implementation)
102
- export function createJWT(payload: any, secret: string, expiresIn: number = 3600): string {
103
- const header = { alg: 'HS256', typ: 'JWT' };
104
- const now = Math.floor(Date.now() / 1000);
105
-
106
- const jwtPayload = {
107
- ...payload,
108
- iat: now,
109
- exp: now + expiresIn
110
- };
111
-
112
- const headerB64 = btoa(JSON.stringify(header));
113
- const payloadB64 = btoa(JSON.stringify(jwtPayload));
114
-
115
- // Simple signature (in production, use proper HMAC-SHA256)
116
- const signature = btoa(`${headerB64}.${payloadB64}.${secret}`);
117
-
118
- return `${headerB64}.${payloadB64}.${signature}`;
119
- }
package/plugins/logger.ts DELETED
@@ -1,109 +0,0 @@
1
- import BXO from '../index';
2
-
3
- interface LoggerOptions {
4
- format?: 'simple' | 'detailed' | 'json';
5
- includeBody?: boolean;
6
- includeHeaders?: boolean;
7
- }
8
-
9
- export function logger(options: LoggerOptions = {}): BXO {
10
- const {
11
- format = 'simple',
12
- includeBody = false,
13
- includeHeaders = false
14
- } = options;
15
-
16
- const loggerInstance = new BXO();
17
-
18
- loggerInstance.onRequest(async (ctx: any) => {
19
- ctx._startTime = Date.now();
20
-
21
- if (format === 'json') {
22
- const logData: any = {
23
- timestamp: new Date().toISOString(),
24
- method: ctx.request.method,
25
- url: ctx.request.url,
26
- type: 'request'
27
- };
28
-
29
- if (includeHeaders) {
30
- logData.headers = Object.fromEntries(ctx.request.headers.entries());
31
- }
32
-
33
- if (includeBody && ctx.body) {
34
- logData.body = ctx.body;
35
- }
36
-
37
- console.log(JSON.stringify(logData));
38
- } else if (format === 'detailed') {
39
- console.log(`→ ${ctx.request.method} ${ctx.request.url}`);
40
- if (includeHeaders) {
41
- console.log(' Headers:', Object.fromEntries(ctx.request.headers.entries()));
42
- }
43
- if (includeBody && ctx.body) {
44
- console.log(' Body:', ctx.body);
45
- }
46
- } else {
47
- console.log(`→ ${ctx.request.method} ${ctx.request.url}`);
48
- }
49
- });
50
-
51
- loggerInstance.onResponse(async (ctx: any, response: any) => {
52
- const duration = Date.now() - (ctx._startTime || 0);
53
- const status = ctx.set.status || 200;
54
-
55
- if (format === 'json') {
56
- const logData: any = {
57
- timestamp: new Date().toISOString(),
58
- method: ctx.request.method,
59
- url: ctx.request.url,
60
- status,
61
- duration: `${duration}ms`,
62
- type: 'response'
63
- };
64
-
65
- if (includeHeaders && ctx.set.headers) {
66
- logData.responseHeaders = ctx.set.headers;
67
- }
68
-
69
- if (includeBody && response) {
70
- logData.response = response;
71
- }
72
-
73
- console.log(JSON.stringify(logData));
74
- } else if (format === 'detailed') {
75
- console.log(`← ${ctx.request.method} ${ctx.request.url} ${status} ${duration}ms`);
76
- if (includeHeaders && ctx.set.headers) {
77
- console.log(' Response Headers:', ctx.set.headers);
78
- }
79
- if (includeBody && response) {
80
- console.log(' Response:', response);
81
- }
82
- } else {
83
- const statusColor = status >= 400 ? '\x1b[31m' : status >= 300 ? '\x1b[33m' : '\x1b[32m';
84
- const resetColor = '\x1b[0m';
85
- console.log(`← ${ctx.request.method} ${ctx.request.url} ${statusColor}${status}${resetColor} ${duration}ms`);
86
- }
87
-
88
- return response;
89
- });
90
-
91
- loggerInstance.onError(async (ctx: any, error: Error) => {
92
- const duration = Date.now() - (ctx._startTime || 0);
93
-
94
- if (format === 'json') {
95
- console.log(JSON.stringify({
96
- timestamp: new Date().toISOString(),
97
- method: ctx.request.method,
98
- url: ctx.request.url,
99
- error: error.message,
100
- duration: `${duration}ms`,
101
- type: 'error'
102
- }));
103
- } else {
104
- console.log(`✗ ${ctx.request.method} ${ctx.request.url} \x1b[31mERROR\x1b[0m ${duration}ms: ${error.message}`);
105
- }
106
- });
107
-
108
- return loggerInstance;
109
- }
@@ -1,140 +0,0 @@
1
- import BXO from '../index';
2
-
3
- interface RateLimitOptions {
4
- max: number;
5
- window: number; // in seconds
6
- keyGenerator?: (ctx: any) => string;
7
- skipSuccessful?: boolean;
8
- skipFailed?: boolean;
9
- exclude?: string[];
10
- message?: string;
11
- statusCode?: number;
12
- }
13
-
14
- class RateLimitStore {
15
- private store = new Map<string, { count: number; resetTime: number }>();
16
-
17
- get(key: string): { count: number; resetTime: number } | undefined {
18
- const entry = this.store.get(key);
19
- if (entry && Date.now() > entry.resetTime) {
20
- this.store.delete(key);
21
- return undefined;
22
- }
23
- return entry;
24
- }
25
-
26
- set(key: string, count: number, resetTime: number): void {
27
- this.store.set(key, { count, resetTime });
28
- }
29
-
30
- increment(key: string, window: number): { count: number; resetTime: number } {
31
- const now = Date.now();
32
- const entry = this.get(key);
33
-
34
- if (!entry) {
35
- const resetTime = now + (window * 1000);
36
- this.set(key, 1, resetTime);
37
- return { count: 1, resetTime };
38
- }
39
-
40
- entry.count++;
41
- this.set(key, entry.count, entry.resetTime);
42
- return entry;
43
- }
44
-
45
- cleanup(): void {
46
- const now = Date.now();
47
- for (const [key, entry] of this.store.entries()) {
48
- if (now > entry.resetTime) {
49
- this.store.delete(key);
50
- }
51
- }
52
- }
53
- }
54
-
55
- export function rateLimit(options: RateLimitOptions): BXO {
56
- const {
57
- max,
58
- window,
59
- keyGenerator = (ctx) => {
60
- // Default: use IP address
61
- return ctx.request.headers.get('x-forwarded-for') ||
62
- ctx.request.headers.get('x-real-ip') ||
63
- 'unknown';
64
- },
65
- skipSuccessful = false,
66
- skipFailed = false,
67
- exclude = [],
68
- message = 'Too many requests',
69
- statusCode = 429
70
- } = options;
71
-
72
- const store = new RateLimitStore();
73
-
74
- // Cleanup expired entries every 5 minutes
75
- setInterval(() => store.cleanup(), 5 * 60 * 1000);
76
-
77
- const rateLimitInstance = new BXO();
78
-
79
- rateLimitInstance.onRequest(async (ctx: any) => {
80
- const url = new URL(ctx.request.url);
81
- const pathname = url.pathname;
82
-
83
- // Skip rate limiting for excluded paths
84
- if (exclude.some(path => {
85
- if (path.includes('*')) {
86
- const regex = new RegExp(path.replace(/\*/g, '.*'));
87
- return regex.test(pathname);
88
- }
89
- return pathname === path || pathname.startsWith(path);
90
- })) {
91
- return;
92
- }
93
-
94
- const key = keyGenerator(ctx);
95
- const entry = store.increment(key, window);
96
-
97
- if (entry.count > max) {
98
- const resetTime = Math.ceil(entry.resetTime / 1000);
99
- throw new Response(JSON.stringify({
100
- error: message,
101
- retryAfter: resetTime - Math.floor(Date.now() / 1000)
102
- }), {
103
- status: statusCode,
104
- headers: {
105
- 'Content-Type': 'application/json',
106
- 'X-RateLimit-Limit': max.toString(),
107
- 'X-RateLimit-Remaining': '0',
108
- 'X-RateLimit-Reset': resetTime.toString(),
109
- 'Retry-After': (resetTime - Math.floor(Date.now() / 1000)).toString()
110
- }
111
- });
112
- }
113
-
114
- // Add rate limit headers
115
- ctx.set.headers = {
116
- ...ctx.set.headers,
117
- 'X-RateLimit-Limit': max.toString(),
118
- 'X-RateLimit-Remaining': Math.max(0, max - entry.count).toString(),
119
- 'X-RateLimit-Reset': Math.ceil(entry.resetTime / 1000).toString()
120
- };
121
- });
122
-
123
- rateLimitInstance.onResponse(async (ctx: any, response: any) => {
124
- const status = ctx.set.status || 200;
125
- const key = keyGenerator(ctx);
126
-
127
- // Optionally skip counting successful or failed requests
128
- if ((skipSuccessful && status < 400) || (skipFailed && status >= 400)) {
129
- // Decrement the counter since we don't want to count this request
130
- const entry = store.get(key);
131
- if (entry && entry.count > 0) {
132
- store.set(key, entry.count - 1, entry.resetTime);
133
- }
134
- }
135
-
136
- return response;
137
- });
138
-
139
- return rateLimitInstance;
140
- }