mbkbucket 4.1.0 → 4.3.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/lib/cli.js +1 -1
- package/lib/src/app.js +13 -25
- package/lib/src/controllers/api.controller.js +2 -1
- package/lib/src/controllers/dashboard.controller.js +2 -2
- package/lib/src/controllers/info.controller.js +2 -1
- package/lib/src/controllers/view.controller.js +2 -1
- package/lib/src/core/metrics.js +134 -134
- package/lib/src/core/storage/capabilities.js +58 -58
- package/lib/src/core/storage/errors.js +57 -57
- package/lib/src/core/storage/gdrive-auth.js +157 -157
- package/lib/src/core/storage/gdrive-provider.js +715 -714
- package/lib/src/core/storage/index.js +17 -17
- package/lib/src/core/storage/local-provider.js +507 -506
- package/lib/src/core/storage/models.js +121 -120
- package/lib/src/core/storage/path-strategy.js +85 -78
- package/lib/src/core/storage/s3-provider.js +472 -471
- package/lib/src/core/storage/storage-manager.js +226 -223
- package/lib/src/core/storage/storage-provider.js +146 -146
- package/lib/src/middleware/error-handler.js +15 -52
- package/lib/src/middleware/rate-limiter.js +1 -1
- package/lib/src/services/storage.service.js +1 -1
- package/lib/src/utils/cleanup-scheduler.js +91 -91
- package/lib/src/utils/helpers.js +2 -45
- package/package.json +3 -2
- package/views/bucket.hbs +4 -4
- package/views/bucketadmincontent.hbs +2 -2
- package/views/mbkbucket_info.hbs +4 -4
|
@@ -1,157 +1,157 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MBKBucket - Google Drive Authentication & OAuth Helpers
|
|
3
|
-
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import crypto from "node:crypto";
|
|
8
|
-
import { createLogger } from "#logger";
|
|
9
|
-
|
|
10
|
-
const debugAuth = createLogger('gdrive-auth');
|
|
11
|
-
|
|
12
|
-
export const GOOGLE_DRIVE_SCOPES = [
|
|
13
|
-
'https://www.googleapis.com/auth/drive',
|
|
14
|
-
'https://www.googleapis.com/auth/drive.file',
|
|
15
|
-
'https://www.googleapis.com/auth/drive.metadata.readonly'
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
export function getGoogleAuthUrl({
|
|
19
|
-
clientId,
|
|
20
|
-
redirectUri,
|
|
21
|
-
state = '',
|
|
22
|
-
scopes = GOOGLE_DRIVE_SCOPES,
|
|
23
|
-
prompt = 'consent',
|
|
24
|
-
accessType = 'offline'
|
|
25
|
-
}) {
|
|
26
|
-
if (!clientId) throw new Error('clientId is required for Google OAuth');
|
|
27
|
-
if (!redirectUri) throw new Error('redirectUri is required for Google OAuth');
|
|
28
|
-
|
|
29
|
-
const params = new URLSearchParams({
|
|
30
|
-
client_id: clientId,
|
|
31
|
-
redirect_uri: redirectUri,
|
|
32
|
-
response_type: 'code',
|
|
33
|
-
scope: Array.isArray(scopes) ? scopes.join(' ') : String(scopes),
|
|
34
|
-
access_type: accessType,
|
|
35
|
-
prompt: prompt,
|
|
36
|
-
include_granted_scopes: 'true',
|
|
37
|
-
...(state && { state })
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function exchangeCodeForTokens({
|
|
44
|
-
code,
|
|
45
|
-
clientId,
|
|
46
|
-
clientSecret,
|
|
47
|
-
redirectUri
|
|
48
|
-
}) {
|
|
49
|
-
if (!code) throw new Error('Authorization code is required');
|
|
50
|
-
if (!clientId || !clientSecret) throw new Error('clientId and clientSecret are required');
|
|
51
|
-
|
|
52
|
-
debugAuth('Exchanging auth code for tokens with clientId=%s', clientId);
|
|
53
|
-
|
|
54
|
-
const body = new URLSearchParams({
|
|
55
|
-
code,
|
|
56
|
-
client_id: clientId,
|
|
57
|
-
client_secret: clientSecret,
|
|
58
|
-
redirect_uri: redirectUri || 'postmessage',
|
|
59
|
-
grant_type: 'authorization_code'
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
63
|
-
method: 'POST',
|
|
64
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
65
|
-
body: body.toString()
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const data = await res.json();
|
|
69
|
-
if (!res.ok || data.error) {
|
|
70
|
-
throw new Error(`Failed to exchange Google OAuth code: ${data.error_description || data.error || res.statusText}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const expiry_date = Date.now() + (Number(data.expires_in) || 3600) * 1000;
|
|
74
|
-
return { ...data, expiry_date };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export async function refreshGoogleAccessToken({
|
|
78
|
-
clientId,
|
|
79
|
-
clientSecret,
|
|
80
|
-
refreshToken
|
|
81
|
-
}) {
|
|
82
|
-
if (!refreshToken) throw new Error('refreshToken is required');
|
|
83
|
-
if (!clientId || !clientSecret) throw new Error('clientId and clientSecret are required');
|
|
84
|
-
|
|
85
|
-
debugAuth('Refreshing access token for clientId=%s', clientId);
|
|
86
|
-
|
|
87
|
-
const body = new URLSearchParams({
|
|
88
|
-
client_id: clientId,
|
|
89
|
-
client_secret: clientSecret,
|
|
90
|
-
refresh_token: refreshToken,
|
|
91
|
-
grant_type: 'refresh_token'
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
95
|
-
method: 'POST',
|
|
96
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
97
|
-
body: body.toString()
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const data = await res.json();
|
|
101
|
-
if (!res.ok || data.error) {
|
|
102
|
-
throw new Error(`Failed to refresh Google access token: ${data.error_description || data.error || res.statusText}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const expiry_date = Date.now() + (Number(data.expires_in) || 3600) * 1000;
|
|
106
|
-
return { ...data, expiry_date };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export async function getServiceAccountAccessToken({
|
|
110
|
-
clientEmail,
|
|
111
|
-
privateKey,
|
|
112
|
-
scopes = GOOGLE_DRIVE_SCOPES
|
|
113
|
-
}) {
|
|
114
|
-
if (!clientEmail || !privateKey) {
|
|
115
|
-
throw new Error('clientEmail and privateKey are required for Service Account authentication');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const now = Math.floor(Date.now() / 1000);
|
|
119
|
-
const header = { alg: 'RS256', typ: 'JWT' };
|
|
120
|
-
const claim = {
|
|
121
|
-
iss: clientEmail,
|
|
122
|
-
scope: Array.isArray(scopes) ? scopes.join(' ') : String(scopes),
|
|
123
|
-
aud: 'https://oauth2.googleapis.com/token',
|
|
124
|
-
exp: now + 3600,
|
|
125
|
-
iat: now
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const base64Url = str => Buffer.from(str).toString('base64url');
|
|
129
|
-
const encodedHeader = base64Url(JSON.stringify(header));
|
|
130
|
-
const encodedClaim = base64Url(JSON.stringify(claim));
|
|
131
|
-
const signatureInput = `${encodedHeader}.${encodedClaim}`;
|
|
132
|
-
|
|
133
|
-
const signer = crypto.createSign('RSA-SHA256');
|
|
134
|
-
signer.update(signatureInput);
|
|
135
|
-
signer.end();
|
|
136
|
-
const signature = signer.sign(privateKey, 'base64url');
|
|
137
|
-
const jwt = `${signatureInput}.${signature}`;
|
|
138
|
-
|
|
139
|
-
const body = new URLSearchParams({
|
|
140
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
|
141
|
-
assertion: jwt
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
145
|
-
method: 'POST',
|
|
146
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
147
|
-
body: body.toString()
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const data = await res.json();
|
|
151
|
-
if (!res.ok || data.error) {
|
|
152
|
-
throw new Error(`Service Account token request failed: ${data.error_description || data.error || res.statusText}`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const expiry_date = Date.now() + (Number(data.expires_in) || 3600) * 1000;
|
|
156
|
-
return { access_token: data.access_token, expires_in: data.expires_in, expiry_date };
|
|
157
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* MBKBucket - Google Drive Authentication & OAuth Helpers
|
|
3
|
+
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import crypto from "node:crypto";
|
|
8
|
+
import { createLogger } from "#logger";
|
|
9
|
+
|
|
10
|
+
const debugAuth = createLogger('gdrive-auth');
|
|
11
|
+
|
|
12
|
+
export const GOOGLE_DRIVE_SCOPES = [
|
|
13
|
+
'https://www.googleapis.com/auth/drive',
|
|
14
|
+
'https://www.googleapis.com/auth/drive.file',
|
|
15
|
+
'https://www.googleapis.com/auth/drive.metadata.readonly'
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function getGoogleAuthUrl({
|
|
19
|
+
clientId,
|
|
20
|
+
redirectUri,
|
|
21
|
+
state = '',
|
|
22
|
+
scopes = GOOGLE_DRIVE_SCOPES,
|
|
23
|
+
prompt = 'consent',
|
|
24
|
+
accessType = 'offline'
|
|
25
|
+
}) {
|
|
26
|
+
if (!clientId) throw new Error('clientId is required for Google OAuth');
|
|
27
|
+
if (!redirectUri) throw new Error('redirectUri is required for Google OAuth');
|
|
28
|
+
|
|
29
|
+
const params = new URLSearchParams({
|
|
30
|
+
client_id: clientId,
|
|
31
|
+
redirect_uri: redirectUri,
|
|
32
|
+
response_type: 'code',
|
|
33
|
+
scope: Array.isArray(scopes) ? scopes.join(' ') : String(scopes),
|
|
34
|
+
access_type: accessType,
|
|
35
|
+
prompt: prompt,
|
|
36
|
+
include_granted_scopes: 'true',
|
|
37
|
+
...(state && { state })
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function exchangeCodeForTokens({
|
|
44
|
+
code,
|
|
45
|
+
clientId,
|
|
46
|
+
clientSecret,
|
|
47
|
+
redirectUri
|
|
48
|
+
}) {
|
|
49
|
+
if (!code) throw new Error('Authorization code is required');
|
|
50
|
+
if (!clientId || !clientSecret) throw new Error('clientId and clientSecret are required');
|
|
51
|
+
|
|
52
|
+
debugAuth('Exchanging auth code for tokens with clientId=%s', clientId);
|
|
53
|
+
|
|
54
|
+
const body = new URLSearchParams({
|
|
55
|
+
code,
|
|
56
|
+
client_id: clientId,
|
|
57
|
+
client_secret: clientSecret,
|
|
58
|
+
redirect_uri: redirectUri || 'postmessage',
|
|
59
|
+
grant_type: 'authorization_code'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
65
|
+
body: body.toString()
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const data = await res.json();
|
|
69
|
+
if (!res.ok || data.error) {
|
|
70
|
+
throw new Error(`Failed to exchange Google OAuth code: ${data.error_description || data.error || res.statusText}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const expiry_date = Date.now() + (Number(data.expires_in) || 3600) * 1000;
|
|
74
|
+
return { ...data, expiry_date };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function refreshGoogleAccessToken({
|
|
78
|
+
clientId,
|
|
79
|
+
clientSecret,
|
|
80
|
+
refreshToken
|
|
81
|
+
}) {
|
|
82
|
+
if (!refreshToken) throw new Error('refreshToken is required');
|
|
83
|
+
if (!clientId || !clientSecret) throw new Error('clientId and clientSecret are required');
|
|
84
|
+
|
|
85
|
+
debugAuth('Refreshing access token for clientId=%s', clientId);
|
|
86
|
+
|
|
87
|
+
const body = new URLSearchParams({
|
|
88
|
+
client_id: clientId,
|
|
89
|
+
client_secret: clientSecret,
|
|
90
|
+
refresh_token: refreshToken,
|
|
91
|
+
grant_type: 'refresh_token'
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
97
|
+
body: body.toString()
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const data = await res.json();
|
|
101
|
+
if (!res.ok || data.error) {
|
|
102
|
+
throw new Error(`Failed to refresh Google access token: ${data.error_description || data.error || res.statusText}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const expiry_date = Date.now() + (Number(data.expires_in) || 3600) * 1000;
|
|
106
|
+
return { ...data, expiry_date };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function getServiceAccountAccessToken({
|
|
110
|
+
clientEmail,
|
|
111
|
+
privateKey,
|
|
112
|
+
scopes = GOOGLE_DRIVE_SCOPES
|
|
113
|
+
}) {
|
|
114
|
+
if (!clientEmail || !privateKey) {
|
|
115
|
+
throw new Error('clientEmail and privateKey are required for Service Account authentication');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const now = Math.floor(Date.now() / 1000);
|
|
119
|
+
const header = { alg: 'RS256', typ: 'JWT' };
|
|
120
|
+
const claim = {
|
|
121
|
+
iss: clientEmail,
|
|
122
|
+
scope: Array.isArray(scopes) ? scopes.join(' ') : String(scopes),
|
|
123
|
+
aud: 'https://oauth2.googleapis.com/token',
|
|
124
|
+
exp: now + 3600,
|
|
125
|
+
iat: now
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const base64Url = str => Buffer.from(str).toString('base64url');
|
|
129
|
+
const encodedHeader = base64Url(JSON.stringify(header));
|
|
130
|
+
const encodedClaim = base64Url(JSON.stringify(claim));
|
|
131
|
+
const signatureInput = `${encodedHeader}.${encodedClaim}`;
|
|
132
|
+
|
|
133
|
+
const signer = crypto.createSign('RSA-SHA256');
|
|
134
|
+
signer.update(signatureInput);
|
|
135
|
+
signer.end();
|
|
136
|
+
const signature = signer.sign(privateKey, 'base64url');
|
|
137
|
+
const jwt = `${signatureInput}.${signature}`;
|
|
138
|
+
|
|
139
|
+
const body = new URLSearchParams({
|
|
140
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
|
141
|
+
assertion: jwt
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
147
|
+
body: body.toString()
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const data = await res.json();
|
|
151
|
+
if (!res.ok || data.error) {
|
|
152
|
+
throw new Error(`Service Account token request failed: ${data.error_description || data.error || res.statusText}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const expiry_date = Date.now() + (Number(data.expires_in) || 3600) * 1000;
|
|
156
|
+
return { access_token: data.access_token, expires_in: data.expires_in, expiry_date };
|
|
157
|
+
}
|