@drawbridge/drawbridge-utils 0.0.5 → 0.0.7
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/dist/axios.cjs +126 -0
- package/dist/axios.d.cts +142 -0
- package/dist/axios.d.ts +142 -0
- package/dist/axios.js +90 -0
- package/dist/circuit.cjs +77 -0
- package/dist/circuit.d.cts +96 -0
- package/dist/circuit.d.ts +96 -0
- package/dist/circuit.js +53 -0
- package/dist/kinde.cjs +56 -0
- package/dist/kinde.d.cts +52 -0
- package/dist/kinde.d.ts +52 -0
- package/dist/kinde.js +32 -0
- package/dist/upload.cjs +65 -0
- package/dist/upload.d.cts +58 -0
- package/dist/upload.d.ts +58 -0
- package/dist/upload.js +38 -0
- package/package.json +24 -1
package/dist/axios.cjs
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// axios.js
|
|
30
|
+
var axios_exports = {};
|
|
31
|
+
__export(axios_exports, {
|
|
32
|
+
axios: () => axios,
|
|
33
|
+
isBlockedIP: () => isBlockedIP,
|
|
34
|
+
safe: () => safe
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(axios_exports);
|
|
37
|
+
var import_axios = __toESM(require("axios"), 1);
|
|
38
|
+
var import_dns = __toESM(require("dns"), 1);
|
|
39
|
+
var import_https = __toESM(require("https"), 1);
|
|
40
|
+
var import_net = __toESM(require("net"), 1);
|
|
41
|
+
var dnsLookup = import_dns.default.promises.lookup;
|
|
42
|
+
var isBlockedIPv4 = (ip) => {
|
|
43
|
+
const parts = ip.split(".").map(Number);
|
|
44
|
+
if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return true;
|
|
45
|
+
const [a, b] = parts;
|
|
46
|
+
if (a === 0) return true;
|
|
47
|
+
if (a === 10) return true;
|
|
48
|
+
if (a === 127) return true;
|
|
49
|
+
if (a === 169 && b === 254) return true;
|
|
50
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
51
|
+
if (a === 192 && b === 168) return true;
|
|
52
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
53
|
+
if (a === 192 && b === 0) return true;
|
|
54
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
55
|
+
if (a === 198 && b === 51) return true;
|
|
56
|
+
if (a === 203 && b === 0) return true;
|
|
57
|
+
if (a >= 224) return true;
|
|
58
|
+
return false;
|
|
59
|
+
};
|
|
60
|
+
var isBlockedIPv6 = (ip) => {
|
|
61
|
+
const lower = ip.toLowerCase();
|
|
62
|
+
if (lower === "::1" || lower === "::") return true;
|
|
63
|
+
if (lower.startsWith("fc") || lower.startsWith("fd")) return true;
|
|
64
|
+
if (/^fe[89ab]/.test(lower)) return true;
|
|
65
|
+
if (lower.startsWith("ff")) return true;
|
|
66
|
+
if (lower.startsWith("::ffff:")) {
|
|
67
|
+
const v4 = lower.slice(7);
|
|
68
|
+
return isBlockedIPv4(v4);
|
|
69
|
+
}
|
|
70
|
+
;
|
|
71
|
+
return false;
|
|
72
|
+
};
|
|
73
|
+
var isBlockedIP = (ip) => {
|
|
74
|
+
const version = import_net.default.isIP(ip);
|
|
75
|
+
if (version === 4) return isBlockedIPv4(ip);
|
|
76
|
+
if (version === 6) return isBlockedIPv6(ip);
|
|
77
|
+
return true;
|
|
78
|
+
};
|
|
79
|
+
var safeGet = async (url, axiosOptions = {}) => {
|
|
80
|
+
let parsed;
|
|
81
|
+
try {
|
|
82
|
+
parsed = new URL(url);
|
|
83
|
+
} catch {
|
|
84
|
+
throw new Error("Invalid URL");
|
|
85
|
+
}
|
|
86
|
+
;
|
|
87
|
+
if (parsed.protocol !== "https:") {
|
|
88
|
+
throw new Error("Only https URLs are allowed");
|
|
89
|
+
}
|
|
90
|
+
;
|
|
91
|
+
const records = await dnsLookup(parsed.hostname, { all: true });
|
|
92
|
+
if (!records || records.length === 0) {
|
|
93
|
+
throw new Error("Host could not be resolved");
|
|
94
|
+
}
|
|
95
|
+
;
|
|
96
|
+
for (const record of records) {
|
|
97
|
+
if (isBlockedIP(record.address)) {
|
|
98
|
+
throw new Error("Host resolves to a blocked IP range");
|
|
99
|
+
}
|
|
100
|
+
;
|
|
101
|
+
}
|
|
102
|
+
;
|
|
103
|
+
const pinned = records[0];
|
|
104
|
+
const agent = new import_https.default.Agent({
|
|
105
|
+
lookup: (hostname, options, callback) => {
|
|
106
|
+
callback(null, pinned.address, pinned.family);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
return import_axios.default.get(url, {
|
|
110
|
+
...axiosOptions,
|
|
111
|
+
httpsAgent: agent,
|
|
112
|
+
maxRedirects: 0,
|
|
113
|
+
maxContentLength: 5 * 1024 * 1024,
|
|
114
|
+
maxBodyLength: 5 * 1024 * 1024
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
var safe = {
|
|
118
|
+
get: safeGet
|
|
119
|
+
};
|
|
120
|
+
var axios = import_axios.default;
|
|
121
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
122
|
+
0 && (module.exports = {
|
|
123
|
+
axios,
|
|
124
|
+
isBlockedIP,
|
|
125
|
+
safe
|
|
126
|
+
});
|
package/dist/axios.d.cts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import axiosLib from 'axios';
|
|
2
|
+
import dns from 'dns';
|
|
3
|
+
import https from 'https';
|
|
4
|
+
import net from 'net';
|
|
5
|
+
|
|
6
|
+
const dnsLookup = dns.promises.lookup;
|
|
7
|
+
|
|
8
|
+
// Reject any IPv4 in the standard private/loopback/link-local/CGN/multicast/reserved
|
|
9
|
+
// ranges. Matches RFC 1918, 5735, 5737, 6598, 6890.
|
|
10
|
+
|
|
11
|
+
const isBlockedIPv4 = ( ip ) => {
|
|
12
|
+
|
|
13
|
+
const parts = ip.split( '.' ).map( Number );
|
|
14
|
+
|
|
15
|
+
if( parts.length !== 4 || parts.some( ( n ) => Number.isNaN( n ) || n < 0 || n > 255 ) ) return true;
|
|
16
|
+
|
|
17
|
+
const [ a, b ] = parts;
|
|
18
|
+
|
|
19
|
+
if( a === 0 ) return true;
|
|
20
|
+
if( a === 10 ) return true;
|
|
21
|
+
if( a === 127 ) return true;
|
|
22
|
+
if( a === 169 && b === 254 ) return true;
|
|
23
|
+
if( a === 172 && b >= 16 && b <= 31 ) return true;
|
|
24
|
+
if( a === 192 && b === 168 ) return true;
|
|
25
|
+
if( a === 100 && b >= 64 && b <= 127 ) return true;
|
|
26
|
+
if( a === 192 && b === 0 ) return true;
|
|
27
|
+
if( a === 198 && ( b === 18 || b === 19 ) ) return true;
|
|
28
|
+
if( a === 198 && b === 51 ) return true;
|
|
29
|
+
if( a === 203 && b === 0 ) return true;
|
|
30
|
+
if( a >= 224 ) return true;
|
|
31
|
+
|
|
32
|
+
return false;
|
|
33
|
+
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const isBlockedIPv6 = ( ip ) => {
|
|
37
|
+
|
|
38
|
+
const lower = ip.toLowerCase();
|
|
39
|
+
|
|
40
|
+
if( lower === '::1' || lower === '::' ) return true;
|
|
41
|
+
|
|
42
|
+
// fc00::/7 — unique local addresses
|
|
43
|
+
if( lower.startsWith( 'fc' ) || lower.startsWith( 'fd' ) ) return true;
|
|
44
|
+
|
|
45
|
+
// fe80::/10 — link-local
|
|
46
|
+
if( /^fe[89ab]/.test( lower ) ) return true;
|
|
47
|
+
|
|
48
|
+
// ff00::/8 — multicast
|
|
49
|
+
if( lower.startsWith( 'ff' ) ) return true;
|
|
50
|
+
|
|
51
|
+
// ::ffff:0:0/96 — IPv4-mapped; check the embedded v4
|
|
52
|
+
if( lower.startsWith( '::ffff:' ) ){
|
|
53
|
+
|
|
54
|
+
const v4 = lower.slice( 7 );
|
|
55
|
+
|
|
56
|
+
return isBlockedIPv4( v4 );
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const isBlockedIP = ( ip ) => {
|
|
64
|
+
|
|
65
|
+
const version = net.isIP( ip );
|
|
66
|
+
|
|
67
|
+
if( version === 4 ) return isBlockedIPv4( ip );
|
|
68
|
+
if( version === 6 ) return isBlockedIPv6( ip );
|
|
69
|
+
|
|
70
|
+
return true;
|
|
71
|
+
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// SSRF-protected GET for user-supplied URLs.
|
|
75
|
+
// Requires https://, resolves all A/AAAA records and rejects if any is in a blocked
|
|
76
|
+
// range, then pins the axios lookup to the validated IP (closes DNS rebinding) and
|
|
77
|
+
// caps response size + disables redirects.
|
|
78
|
+
const safeGet = async ( url, axiosOptions = {} ) => {
|
|
79
|
+
|
|
80
|
+
let parsed;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
|
|
84
|
+
parsed = new URL( url );
|
|
85
|
+
|
|
86
|
+
} catch {
|
|
87
|
+
|
|
88
|
+
throw new Error( 'Invalid URL' );
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
if( parsed.protocol !== 'https:' ){
|
|
92
|
+
|
|
93
|
+
throw new Error( 'Only https URLs are allowed' );
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
const records = await dnsLookup( parsed.hostname, { all : true } );
|
|
97
|
+
|
|
98
|
+
if( ! records || records.length === 0 ){
|
|
99
|
+
|
|
100
|
+
throw new Error( 'Host could not be resolved' );
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
for( const record of records ){
|
|
104
|
+
|
|
105
|
+
if( isBlockedIP( record.address ) ){
|
|
106
|
+
|
|
107
|
+
throw new Error( 'Host resolves to a blocked IP range' );
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const pinned = records[ 0 ];
|
|
112
|
+
|
|
113
|
+
const agent = new https.Agent({
|
|
114
|
+
lookup : ( hostname, options, callback ) => {
|
|
115
|
+
|
|
116
|
+
callback( null, pinned.address, pinned.family );
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return axiosLib.get( url, {
|
|
122
|
+
...axiosOptions,
|
|
123
|
+
httpsAgent : agent,
|
|
124
|
+
maxRedirects : 0,
|
|
125
|
+
maxContentLength : 5 * 1024 * 1024,
|
|
126
|
+
maxBodyLength : 5 * 1024 * 1024
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// SSRF-protected namespace. Use for ANY URL that comes from request input.
|
|
132
|
+
// await safe.get( userUrl, options );
|
|
133
|
+
const safe = {
|
|
134
|
+
get : safeGet
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Raw axios passthrough. Use for trusted/hardcoded URLs (Google APIs, Stripe, etc.).
|
|
138
|
+
// await axios.get( 'https://api.stripe.com/...' );
|
|
139
|
+
// await axios.post( ... );
|
|
140
|
+
const axios = axiosLib;
|
|
141
|
+
|
|
142
|
+
export { axios, isBlockedIP, safe };
|
package/dist/axios.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import axiosLib from 'axios';
|
|
2
|
+
import dns from 'dns';
|
|
3
|
+
import https from 'https';
|
|
4
|
+
import net from 'net';
|
|
5
|
+
|
|
6
|
+
const dnsLookup = dns.promises.lookup;
|
|
7
|
+
|
|
8
|
+
// Reject any IPv4 in the standard private/loopback/link-local/CGN/multicast/reserved
|
|
9
|
+
// ranges. Matches RFC 1918, 5735, 5737, 6598, 6890.
|
|
10
|
+
|
|
11
|
+
const isBlockedIPv4 = ( ip ) => {
|
|
12
|
+
|
|
13
|
+
const parts = ip.split( '.' ).map( Number );
|
|
14
|
+
|
|
15
|
+
if( parts.length !== 4 || parts.some( ( n ) => Number.isNaN( n ) || n < 0 || n > 255 ) ) return true;
|
|
16
|
+
|
|
17
|
+
const [ a, b ] = parts;
|
|
18
|
+
|
|
19
|
+
if( a === 0 ) return true;
|
|
20
|
+
if( a === 10 ) return true;
|
|
21
|
+
if( a === 127 ) return true;
|
|
22
|
+
if( a === 169 && b === 254 ) return true;
|
|
23
|
+
if( a === 172 && b >= 16 && b <= 31 ) return true;
|
|
24
|
+
if( a === 192 && b === 168 ) return true;
|
|
25
|
+
if( a === 100 && b >= 64 && b <= 127 ) return true;
|
|
26
|
+
if( a === 192 && b === 0 ) return true;
|
|
27
|
+
if( a === 198 && ( b === 18 || b === 19 ) ) return true;
|
|
28
|
+
if( a === 198 && b === 51 ) return true;
|
|
29
|
+
if( a === 203 && b === 0 ) return true;
|
|
30
|
+
if( a >= 224 ) return true;
|
|
31
|
+
|
|
32
|
+
return false;
|
|
33
|
+
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const isBlockedIPv6 = ( ip ) => {
|
|
37
|
+
|
|
38
|
+
const lower = ip.toLowerCase();
|
|
39
|
+
|
|
40
|
+
if( lower === '::1' || lower === '::' ) return true;
|
|
41
|
+
|
|
42
|
+
// fc00::/7 — unique local addresses
|
|
43
|
+
if( lower.startsWith( 'fc' ) || lower.startsWith( 'fd' ) ) return true;
|
|
44
|
+
|
|
45
|
+
// fe80::/10 — link-local
|
|
46
|
+
if( /^fe[89ab]/.test( lower ) ) return true;
|
|
47
|
+
|
|
48
|
+
// ff00::/8 — multicast
|
|
49
|
+
if( lower.startsWith( 'ff' ) ) return true;
|
|
50
|
+
|
|
51
|
+
// ::ffff:0:0/96 — IPv4-mapped; check the embedded v4
|
|
52
|
+
if( lower.startsWith( '::ffff:' ) ){
|
|
53
|
+
|
|
54
|
+
const v4 = lower.slice( 7 );
|
|
55
|
+
|
|
56
|
+
return isBlockedIPv4( v4 );
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const isBlockedIP = ( ip ) => {
|
|
64
|
+
|
|
65
|
+
const version = net.isIP( ip );
|
|
66
|
+
|
|
67
|
+
if( version === 4 ) return isBlockedIPv4( ip );
|
|
68
|
+
if( version === 6 ) return isBlockedIPv6( ip );
|
|
69
|
+
|
|
70
|
+
return true;
|
|
71
|
+
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// SSRF-protected GET for user-supplied URLs.
|
|
75
|
+
// Requires https://, resolves all A/AAAA records and rejects if any is in a blocked
|
|
76
|
+
// range, then pins the axios lookup to the validated IP (closes DNS rebinding) and
|
|
77
|
+
// caps response size + disables redirects.
|
|
78
|
+
const safeGet = async ( url, axiosOptions = {} ) => {
|
|
79
|
+
|
|
80
|
+
let parsed;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
|
|
84
|
+
parsed = new URL( url );
|
|
85
|
+
|
|
86
|
+
} catch {
|
|
87
|
+
|
|
88
|
+
throw new Error( 'Invalid URL' );
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
if( parsed.protocol !== 'https:' ){
|
|
92
|
+
|
|
93
|
+
throw new Error( 'Only https URLs are allowed' );
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
const records = await dnsLookup( parsed.hostname, { all : true } );
|
|
97
|
+
|
|
98
|
+
if( ! records || records.length === 0 ){
|
|
99
|
+
|
|
100
|
+
throw new Error( 'Host could not be resolved' );
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
for( const record of records ){
|
|
104
|
+
|
|
105
|
+
if( isBlockedIP( record.address ) ){
|
|
106
|
+
|
|
107
|
+
throw new Error( 'Host resolves to a blocked IP range' );
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const pinned = records[ 0 ];
|
|
112
|
+
|
|
113
|
+
const agent = new https.Agent({
|
|
114
|
+
lookup : ( hostname, options, callback ) => {
|
|
115
|
+
|
|
116
|
+
callback( null, pinned.address, pinned.family );
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return axiosLib.get( url, {
|
|
122
|
+
...axiosOptions,
|
|
123
|
+
httpsAgent : agent,
|
|
124
|
+
maxRedirects : 0,
|
|
125
|
+
maxContentLength : 5 * 1024 * 1024,
|
|
126
|
+
maxBodyLength : 5 * 1024 * 1024
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// SSRF-protected namespace. Use for ANY URL that comes from request input.
|
|
132
|
+
// await safe.get( userUrl, options );
|
|
133
|
+
const safe = {
|
|
134
|
+
get : safeGet
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Raw axios passthrough. Use for trusted/hardcoded URLs (Google APIs, Stripe, etc.).
|
|
138
|
+
// await axios.get( 'https://api.stripe.com/...' );
|
|
139
|
+
// await axios.post( ... );
|
|
140
|
+
const axios = axiosLib;
|
|
141
|
+
|
|
142
|
+
export { axios, isBlockedIP, safe };
|
package/dist/axios.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// axios.js
|
|
2
|
+
import axiosLib from "axios";
|
|
3
|
+
import dns from "dns";
|
|
4
|
+
import https from "https";
|
|
5
|
+
import net from "net";
|
|
6
|
+
var dnsLookup = dns.promises.lookup;
|
|
7
|
+
var isBlockedIPv4 = (ip) => {
|
|
8
|
+
const parts = ip.split(".").map(Number);
|
|
9
|
+
if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return true;
|
|
10
|
+
const [a, b] = parts;
|
|
11
|
+
if (a === 0) return true;
|
|
12
|
+
if (a === 10) return true;
|
|
13
|
+
if (a === 127) return true;
|
|
14
|
+
if (a === 169 && b === 254) return true;
|
|
15
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
16
|
+
if (a === 192 && b === 168) return true;
|
|
17
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
18
|
+
if (a === 192 && b === 0) return true;
|
|
19
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
20
|
+
if (a === 198 && b === 51) return true;
|
|
21
|
+
if (a === 203 && b === 0) return true;
|
|
22
|
+
if (a >= 224) return true;
|
|
23
|
+
return false;
|
|
24
|
+
};
|
|
25
|
+
var isBlockedIPv6 = (ip) => {
|
|
26
|
+
const lower = ip.toLowerCase();
|
|
27
|
+
if (lower === "::1" || lower === "::") return true;
|
|
28
|
+
if (lower.startsWith("fc") || lower.startsWith("fd")) return true;
|
|
29
|
+
if (/^fe[89ab]/.test(lower)) return true;
|
|
30
|
+
if (lower.startsWith("ff")) return true;
|
|
31
|
+
if (lower.startsWith("::ffff:")) {
|
|
32
|
+
const v4 = lower.slice(7);
|
|
33
|
+
return isBlockedIPv4(v4);
|
|
34
|
+
}
|
|
35
|
+
;
|
|
36
|
+
return false;
|
|
37
|
+
};
|
|
38
|
+
var isBlockedIP = (ip) => {
|
|
39
|
+
const version = net.isIP(ip);
|
|
40
|
+
if (version === 4) return isBlockedIPv4(ip);
|
|
41
|
+
if (version === 6) return isBlockedIPv6(ip);
|
|
42
|
+
return true;
|
|
43
|
+
};
|
|
44
|
+
var safeGet = async (url, axiosOptions = {}) => {
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = new URL(url);
|
|
48
|
+
} catch {
|
|
49
|
+
throw new Error("Invalid URL");
|
|
50
|
+
}
|
|
51
|
+
;
|
|
52
|
+
if (parsed.protocol !== "https:") {
|
|
53
|
+
throw new Error("Only https URLs are allowed");
|
|
54
|
+
}
|
|
55
|
+
;
|
|
56
|
+
const records = await dnsLookup(parsed.hostname, { all: true });
|
|
57
|
+
if (!records || records.length === 0) {
|
|
58
|
+
throw new Error("Host could not be resolved");
|
|
59
|
+
}
|
|
60
|
+
;
|
|
61
|
+
for (const record of records) {
|
|
62
|
+
if (isBlockedIP(record.address)) {
|
|
63
|
+
throw new Error("Host resolves to a blocked IP range");
|
|
64
|
+
}
|
|
65
|
+
;
|
|
66
|
+
}
|
|
67
|
+
;
|
|
68
|
+
const pinned = records[0];
|
|
69
|
+
const agent = new https.Agent({
|
|
70
|
+
lookup: (hostname, options, callback) => {
|
|
71
|
+
callback(null, pinned.address, pinned.family);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return axiosLib.get(url, {
|
|
75
|
+
...axiosOptions,
|
|
76
|
+
httpsAgent: agent,
|
|
77
|
+
maxRedirects: 0,
|
|
78
|
+
maxContentLength: 5 * 1024 * 1024,
|
|
79
|
+
maxBodyLength: 5 * 1024 * 1024
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
var safe = {
|
|
83
|
+
get: safeGet
|
|
84
|
+
};
|
|
85
|
+
var axios = axiosLib;
|
|
86
|
+
export {
|
|
87
|
+
axios,
|
|
88
|
+
isBlockedIP,
|
|
89
|
+
safe
|
|
90
|
+
};
|
package/dist/circuit.cjs
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// circuit.js
|
|
20
|
+
var circuit_exports = {};
|
|
21
|
+
__export(circuit_exports, {
|
|
22
|
+
circuit: () => circuit
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(circuit_exports);
|
|
25
|
+
var CLOSED = "CLOSED";
|
|
26
|
+
var OPEN = "OPEN";
|
|
27
|
+
var HALF_OPEN = "HALF_OPEN";
|
|
28
|
+
var circuit = ({
|
|
29
|
+
name,
|
|
30
|
+
threshold = 5,
|
|
31
|
+
timeout = 3e4
|
|
32
|
+
}) => {
|
|
33
|
+
let state = CLOSED;
|
|
34
|
+
let failures = 0;
|
|
35
|
+
let openedAt = null;
|
|
36
|
+
const trip = () => {
|
|
37
|
+
state = OPEN;
|
|
38
|
+
openedAt = Date.now();
|
|
39
|
+
console.error(`Circuit breaker OPEN: ${name}`);
|
|
40
|
+
};
|
|
41
|
+
const reset = () => {
|
|
42
|
+
state = CLOSED;
|
|
43
|
+
failures = 0;
|
|
44
|
+
openedAt = null;
|
|
45
|
+
console.log(`Circuit breaker CLOSED: ${name}`);
|
|
46
|
+
};
|
|
47
|
+
return async (fn) => {
|
|
48
|
+
if (state === OPEN) {
|
|
49
|
+
if (Date.now() - openedAt >= timeout) {
|
|
50
|
+
state = HALF_OPEN;
|
|
51
|
+
} else {
|
|
52
|
+
const error = new Error(`${name} is temporarily unavailable`);
|
|
53
|
+
error.status = 503;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const result = await fn();
|
|
59
|
+
if (state === HALF_OPEN) {
|
|
60
|
+
reset();
|
|
61
|
+
} else {
|
|
62
|
+
failures = 0;
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
failures++;
|
|
67
|
+
if (state === HALF_OPEN || failures >= threshold) {
|
|
68
|
+
trip();
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
75
|
+
0 && (module.exports = {
|
|
76
|
+
circuit
|
|
77
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Simple circuit breaker for wrapped async operations.
|
|
2
|
+
//
|
|
3
|
+
// const breaker = circuit({ name : 'kinde-auth', threshold : 5, timeout : 30000 });
|
|
4
|
+
// const result = await breaker( () => validateToken( ... ) );
|
|
5
|
+
//
|
|
6
|
+
// States: CLOSED → OPEN (after `threshold` consecutive failures) → HALF_OPEN
|
|
7
|
+
// (after `timeout` ms; next call probes recovery) → CLOSED (on success).
|
|
8
|
+
//
|
|
9
|
+
// IMPORTANT: state is per-process (per closure). With multiple replicas, each has
|
|
10
|
+
// its own counters — you can get N × threshold failures globally before any replica
|
|
11
|
+
// trips. For higher-stakes calls, back this with Redis.
|
|
12
|
+
|
|
13
|
+
const CLOSED = 'CLOSED';
|
|
14
|
+
const OPEN = 'OPEN';
|
|
15
|
+
const HALF_OPEN = 'HALF_OPEN';
|
|
16
|
+
|
|
17
|
+
const circuit = ({
|
|
18
|
+
name,
|
|
19
|
+
threshold = 5,
|
|
20
|
+
timeout = 30000
|
|
21
|
+
}) => {
|
|
22
|
+
|
|
23
|
+
let state = CLOSED;
|
|
24
|
+
let failures = 0;
|
|
25
|
+
let openedAt = null;
|
|
26
|
+
|
|
27
|
+
const trip = () => {
|
|
28
|
+
|
|
29
|
+
state = OPEN;
|
|
30
|
+
openedAt = Date.now();
|
|
31
|
+
console.error( `Circuit breaker OPEN: ${ name }` );
|
|
32
|
+
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const reset = () => {
|
|
36
|
+
|
|
37
|
+
state = CLOSED;
|
|
38
|
+
failures = 0;
|
|
39
|
+
openedAt = null;
|
|
40
|
+
console.log( `Circuit breaker CLOSED: ${ name }` );
|
|
41
|
+
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return async ( fn ) => {
|
|
45
|
+
|
|
46
|
+
if( state === OPEN ){
|
|
47
|
+
|
|
48
|
+
if( Date.now() - openedAt >= timeout ){
|
|
49
|
+
|
|
50
|
+
state = HALF_OPEN;
|
|
51
|
+
|
|
52
|
+
} else {
|
|
53
|
+
|
|
54
|
+
const error = new Error( `${ name } is temporarily unavailable` );
|
|
55
|
+
error.status = 503;
|
|
56
|
+
throw error;
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
|
|
64
|
+
const result = await fn();
|
|
65
|
+
|
|
66
|
+
if( state === HALF_OPEN ){
|
|
67
|
+
|
|
68
|
+
reset();
|
|
69
|
+
|
|
70
|
+
} else {
|
|
71
|
+
|
|
72
|
+
failures = 0;
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
|
|
78
|
+
} catch ( error ) {
|
|
79
|
+
|
|
80
|
+
failures++;
|
|
81
|
+
|
|
82
|
+
if( state === HALF_OPEN || failures >= threshold ){
|
|
83
|
+
|
|
84
|
+
trip();
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
throw error;
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export { circuit };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Simple circuit breaker for wrapped async operations.
|
|
2
|
+
//
|
|
3
|
+
// const breaker = circuit({ name : 'kinde-auth', threshold : 5, timeout : 30000 });
|
|
4
|
+
// const result = await breaker( () => validateToken( ... ) );
|
|
5
|
+
//
|
|
6
|
+
// States: CLOSED → OPEN (after `threshold` consecutive failures) → HALF_OPEN
|
|
7
|
+
// (after `timeout` ms; next call probes recovery) → CLOSED (on success).
|
|
8
|
+
//
|
|
9
|
+
// IMPORTANT: state is per-process (per closure). With multiple replicas, each has
|
|
10
|
+
// its own counters — you can get N × threshold failures globally before any replica
|
|
11
|
+
// trips. For higher-stakes calls, back this with Redis.
|
|
12
|
+
|
|
13
|
+
const CLOSED = 'CLOSED';
|
|
14
|
+
const OPEN = 'OPEN';
|
|
15
|
+
const HALF_OPEN = 'HALF_OPEN';
|
|
16
|
+
|
|
17
|
+
const circuit = ({
|
|
18
|
+
name,
|
|
19
|
+
threshold = 5,
|
|
20
|
+
timeout = 30000
|
|
21
|
+
}) => {
|
|
22
|
+
|
|
23
|
+
let state = CLOSED;
|
|
24
|
+
let failures = 0;
|
|
25
|
+
let openedAt = null;
|
|
26
|
+
|
|
27
|
+
const trip = () => {
|
|
28
|
+
|
|
29
|
+
state = OPEN;
|
|
30
|
+
openedAt = Date.now();
|
|
31
|
+
console.error( `Circuit breaker OPEN: ${ name }` );
|
|
32
|
+
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const reset = () => {
|
|
36
|
+
|
|
37
|
+
state = CLOSED;
|
|
38
|
+
failures = 0;
|
|
39
|
+
openedAt = null;
|
|
40
|
+
console.log( `Circuit breaker CLOSED: ${ name }` );
|
|
41
|
+
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return async ( fn ) => {
|
|
45
|
+
|
|
46
|
+
if( state === OPEN ){
|
|
47
|
+
|
|
48
|
+
if( Date.now() - openedAt >= timeout ){
|
|
49
|
+
|
|
50
|
+
state = HALF_OPEN;
|
|
51
|
+
|
|
52
|
+
} else {
|
|
53
|
+
|
|
54
|
+
const error = new Error( `${ name } is temporarily unavailable` );
|
|
55
|
+
error.status = 503;
|
|
56
|
+
throw error;
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
|
|
64
|
+
const result = await fn();
|
|
65
|
+
|
|
66
|
+
if( state === HALF_OPEN ){
|
|
67
|
+
|
|
68
|
+
reset();
|
|
69
|
+
|
|
70
|
+
} else {
|
|
71
|
+
|
|
72
|
+
failures = 0;
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
|
|
78
|
+
} catch ( error ) {
|
|
79
|
+
|
|
80
|
+
failures++;
|
|
81
|
+
|
|
82
|
+
if( state === HALF_OPEN || failures >= threshold ){
|
|
83
|
+
|
|
84
|
+
trip();
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
throw error;
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export { circuit };
|
package/dist/circuit.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// circuit.js
|
|
2
|
+
var CLOSED = "CLOSED";
|
|
3
|
+
var OPEN = "OPEN";
|
|
4
|
+
var HALF_OPEN = "HALF_OPEN";
|
|
5
|
+
var circuit = ({
|
|
6
|
+
name,
|
|
7
|
+
threshold = 5,
|
|
8
|
+
timeout = 3e4
|
|
9
|
+
}) => {
|
|
10
|
+
let state = CLOSED;
|
|
11
|
+
let failures = 0;
|
|
12
|
+
let openedAt = null;
|
|
13
|
+
const trip = () => {
|
|
14
|
+
state = OPEN;
|
|
15
|
+
openedAt = Date.now();
|
|
16
|
+
console.error(`Circuit breaker OPEN: ${name}`);
|
|
17
|
+
};
|
|
18
|
+
const reset = () => {
|
|
19
|
+
state = CLOSED;
|
|
20
|
+
failures = 0;
|
|
21
|
+
openedAt = null;
|
|
22
|
+
console.log(`Circuit breaker CLOSED: ${name}`);
|
|
23
|
+
};
|
|
24
|
+
return async (fn) => {
|
|
25
|
+
if (state === OPEN) {
|
|
26
|
+
if (Date.now() - openedAt >= timeout) {
|
|
27
|
+
state = HALF_OPEN;
|
|
28
|
+
} else {
|
|
29
|
+
const error = new Error(`${name} is temporarily unavailable`);
|
|
30
|
+
error.status = 503;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const result = await fn();
|
|
36
|
+
if (state === HALF_OPEN) {
|
|
37
|
+
reset();
|
|
38
|
+
} else {
|
|
39
|
+
failures = 0;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
failures++;
|
|
44
|
+
if (state === HALF_OPEN || failures >= threshold) {
|
|
45
|
+
trip();
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export {
|
|
52
|
+
circuit
|
|
53
|
+
};
|
package/dist/kinde.cjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// kinde.js
|
|
20
|
+
var kinde_exports = {};
|
|
21
|
+
__export(kinde_exports, {
|
|
22
|
+
verifyToken: () => verifyToken
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(kinde_exports);
|
|
25
|
+
var import_jwt_decoder = require("@kinde/jwt-decoder");
|
|
26
|
+
var import_jwt_validator = require("@kinde/jwt-validator");
|
|
27
|
+
var normalizeDomain = (raw) => {
|
|
28
|
+
const trimmed = (raw || "").trim().replace(/\/+$/, "");
|
|
29
|
+
if (!trimmed) return trimmed;
|
|
30
|
+
return /^https?:\/\//.test(trimmed) ? trimmed : "https://" + trimmed;
|
|
31
|
+
};
|
|
32
|
+
var verifyToken = async (rawToken, { domain } = {}) => {
|
|
33
|
+
if (!rawToken) {
|
|
34
|
+
throw new Error("No token");
|
|
35
|
+
}
|
|
36
|
+
;
|
|
37
|
+
const token = rawToken.startsWith("Bearer ") ? rawToken.slice(7) : rawToken;
|
|
38
|
+
const validation = await (0, import_jwt_validator.validateToken)({
|
|
39
|
+
domain: normalizeDomain(domain),
|
|
40
|
+
token
|
|
41
|
+
});
|
|
42
|
+
if (!(validation == null ? void 0 : validation.valid)) {
|
|
43
|
+
throw new Error((validation == null ? void 0 : validation.message) || "Invalid token");
|
|
44
|
+
}
|
|
45
|
+
;
|
|
46
|
+
const decoded = (0, import_jwt_decoder.jwtDecoder)(token);
|
|
47
|
+
if (!(decoded == null ? void 0 : decoded.sub)) {
|
|
48
|
+
throw new Error("Invalid token payload");
|
|
49
|
+
}
|
|
50
|
+
;
|
|
51
|
+
return decoded;
|
|
52
|
+
};
|
|
53
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
54
|
+
0 && (module.exports = {
|
|
55
|
+
verifyToken
|
|
56
|
+
});
|
package/dist/kinde.d.cts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jwtDecoder } from '@kinde/jwt-decoder';
|
|
2
|
+
import { validateToken } from '@kinde/jwt-validator';
|
|
3
|
+
|
|
4
|
+
const normalizeDomain = ( raw ) => {
|
|
5
|
+
|
|
6
|
+
const trimmed = ( raw || '' ).trim().replace( /\/+$/, '' );
|
|
7
|
+
|
|
8
|
+
if( ! trimmed ) return trimmed;
|
|
9
|
+
|
|
10
|
+
return /^https?:\/\//.test( trimmed ) ? trimmed : 'https://' + trimmed;
|
|
11
|
+
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Verifies a Kinde access token cryptographically via JWKS and returns the decoded
|
|
15
|
+
// payload. Throws on any failure (missing/invalid signature, missing sub claim, etc.).
|
|
16
|
+
// Caller does user lookup + service-specific glue around this.
|
|
17
|
+
//
|
|
18
|
+
// const decoded = await verifyToken( token, { domain : process.env.KINDE_DOMAIN } );
|
|
19
|
+
// decoded.sub // Kinde user id
|
|
20
|
+
// decoded.org_code // org code
|
|
21
|
+
// decoded.permissions // permissions array
|
|
22
|
+
const verifyToken = async ( rawToken, { domain } = {} ) => {
|
|
23
|
+
|
|
24
|
+
if( ! rawToken ){
|
|
25
|
+
|
|
26
|
+
throw new Error( 'No token' );
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
const token = rawToken.startsWith( 'Bearer ' ) ? rawToken.slice( 7 ) : rawToken;
|
|
30
|
+
|
|
31
|
+
const validation = await validateToken({
|
|
32
|
+
domain : normalizeDomain( domain ),
|
|
33
|
+
token
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if( ! validation?.valid ){
|
|
37
|
+
|
|
38
|
+
throw new Error( validation?.message || 'Invalid token' );
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
const decoded = jwtDecoder( token );
|
|
42
|
+
|
|
43
|
+
if( ! decoded?.sub ){
|
|
44
|
+
|
|
45
|
+
throw new Error( 'Invalid token payload' );
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
return decoded;
|
|
49
|
+
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export { verifyToken };
|
package/dist/kinde.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jwtDecoder } from '@kinde/jwt-decoder';
|
|
2
|
+
import { validateToken } from '@kinde/jwt-validator';
|
|
3
|
+
|
|
4
|
+
const normalizeDomain = ( raw ) => {
|
|
5
|
+
|
|
6
|
+
const trimmed = ( raw || '' ).trim().replace( /\/+$/, '' );
|
|
7
|
+
|
|
8
|
+
if( ! trimmed ) return trimmed;
|
|
9
|
+
|
|
10
|
+
return /^https?:\/\//.test( trimmed ) ? trimmed : 'https://' + trimmed;
|
|
11
|
+
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Verifies a Kinde access token cryptographically via JWKS and returns the decoded
|
|
15
|
+
// payload. Throws on any failure (missing/invalid signature, missing sub claim, etc.).
|
|
16
|
+
// Caller does user lookup + service-specific glue around this.
|
|
17
|
+
//
|
|
18
|
+
// const decoded = await verifyToken( token, { domain : process.env.KINDE_DOMAIN } );
|
|
19
|
+
// decoded.sub // Kinde user id
|
|
20
|
+
// decoded.org_code // org code
|
|
21
|
+
// decoded.permissions // permissions array
|
|
22
|
+
const verifyToken = async ( rawToken, { domain } = {} ) => {
|
|
23
|
+
|
|
24
|
+
if( ! rawToken ){
|
|
25
|
+
|
|
26
|
+
throw new Error( 'No token' );
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
const token = rawToken.startsWith( 'Bearer ' ) ? rawToken.slice( 7 ) : rawToken;
|
|
30
|
+
|
|
31
|
+
const validation = await validateToken({
|
|
32
|
+
domain : normalizeDomain( domain ),
|
|
33
|
+
token
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if( ! validation?.valid ){
|
|
37
|
+
|
|
38
|
+
throw new Error( validation?.message || 'Invalid token' );
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
const decoded = jwtDecoder( token );
|
|
42
|
+
|
|
43
|
+
if( ! decoded?.sub ){
|
|
44
|
+
|
|
45
|
+
throw new Error( 'Invalid token payload' );
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
return decoded;
|
|
49
|
+
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export { verifyToken };
|
package/dist/kinde.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// kinde.js
|
|
2
|
+
import { jwtDecoder } from "@kinde/jwt-decoder";
|
|
3
|
+
import { validateToken } from "@kinde/jwt-validator";
|
|
4
|
+
var normalizeDomain = (raw) => {
|
|
5
|
+
const trimmed = (raw || "").trim().replace(/\/+$/, "");
|
|
6
|
+
if (!trimmed) return trimmed;
|
|
7
|
+
return /^https?:\/\//.test(trimmed) ? trimmed : "https://" + trimmed;
|
|
8
|
+
};
|
|
9
|
+
var verifyToken = async (rawToken, { domain } = {}) => {
|
|
10
|
+
if (!rawToken) {
|
|
11
|
+
throw new Error("No token");
|
|
12
|
+
}
|
|
13
|
+
;
|
|
14
|
+
const token = rawToken.startsWith("Bearer ") ? rawToken.slice(7) : rawToken;
|
|
15
|
+
const validation = await validateToken({
|
|
16
|
+
domain: normalizeDomain(domain),
|
|
17
|
+
token
|
|
18
|
+
});
|
|
19
|
+
if (!(validation == null ? void 0 : validation.valid)) {
|
|
20
|
+
throw new Error((validation == null ? void 0 : validation.message) || "Invalid token");
|
|
21
|
+
}
|
|
22
|
+
;
|
|
23
|
+
const decoded = jwtDecoder(token);
|
|
24
|
+
if (!(decoded == null ? void 0 : decoded.sub)) {
|
|
25
|
+
throw new Error("Invalid token payload");
|
|
26
|
+
}
|
|
27
|
+
;
|
|
28
|
+
return decoded;
|
|
29
|
+
};
|
|
30
|
+
export {
|
|
31
|
+
verifyToken
|
|
32
|
+
};
|
package/dist/upload.cjs
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// upload.js
|
|
20
|
+
var upload_exports = {};
|
|
21
|
+
__export(upload_exports, {
|
|
22
|
+
allowedMimes: () => allowedMimes,
|
|
23
|
+
allowedUploadTypes: () => allowedUploadTypes,
|
|
24
|
+
isAllowedMime: () => isAllowedMime,
|
|
25
|
+
resolveUploadType: () => resolveUploadType
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(upload_exports);
|
|
28
|
+
var allowedUploadTypes = {
|
|
29
|
+
application: {
|
|
30
|
+
pdf: "application/pdf"
|
|
31
|
+
},
|
|
32
|
+
image: {
|
|
33
|
+
jpeg: "image/jpeg",
|
|
34
|
+
jpg: "image/jpeg",
|
|
35
|
+
png: "image/png",
|
|
36
|
+
webp: "image/webp"
|
|
37
|
+
},
|
|
38
|
+
video: {
|
|
39
|
+
mp4: "video/mp4"
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var allowedMimes = new Set(
|
|
43
|
+
Object.values(allowedUploadTypes).flatMap(
|
|
44
|
+
(extensions) => Object.values(extensions)
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
var resolveUploadType = (type, extension) => {
|
|
48
|
+
var _a;
|
|
49
|
+
const mime = (_a = allowedUploadTypes == null ? void 0 : allowedUploadTypes[type]) == null ? void 0 : _a[extension];
|
|
50
|
+
if (!mime) {
|
|
51
|
+
const error = new Error("Unsupported upload type or extension");
|
|
52
|
+
error.status = 400;
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
;
|
|
56
|
+
return mime;
|
|
57
|
+
};
|
|
58
|
+
var isAllowedMime = (mime) => allowedMimes.has(mime);
|
|
59
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
60
|
+
0 && (module.exports = {
|
|
61
|
+
allowedMimes,
|
|
62
|
+
allowedUploadTypes,
|
|
63
|
+
isAllowedMime,
|
|
64
|
+
resolveUploadType
|
|
65
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Source-of-truth allowlist for file upload types accepted across drawbridge.
|
|
2
|
+
//
|
|
3
|
+
// Two consumer shapes:
|
|
4
|
+
//
|
|
5
|
+
// 1. The API needs `(type, extension) → mime` when serving an upload route
|
|
6
|
+
// where the URL carries those params. Use `resolveUploadType( type, extension )`.
|
|
7
|
+
// Throws { status: 400 } on anything not in the allowlist.
|
|
8
|
+
//
|
|
9
|
+
// 2. The sync worker downstream needs to validate that a `mimetype` flowing
|
|
10
|
+
// from the DB / job payload is one of the canonical values before passing
|
|
11
|
+
// it to S3 as a ContentType header. Use `isAllowedMime( mime )`.
|
|
12
|
+
//
|
|
13
|
+
// Adding a new accepted upload format means updating `allowedUploadTypes` here
|
|
14
|
+
// AND publishing a new utils version. Both api and sync inherit the change.
|
|
15
|
+
//
|
|
16
|
+
// Why both helpers and not just one: api routes know (type, extension) but not
|
|
17
|
+
// the canonical mime; sync workers have the mime but not the original type tuple.
|
|
18
|
+
|
|
19
|
+
const allowedUploadTypes = {
|
|
20
|
+
application : {
|
|
21
|
+
pdf : 'application/pdf'
|
|
22
|
+
},
|
|
23
|
+
image : {
|
|
24
|
+
jpeg : 'image/jpeg',
|
|
25
|
+
jpg : 'image/jpeg',
|
|
26
|
+
png : 'image/png',
|
|
27
|
+
webp : 'image/webp'
|
|
28
|
+
},
|
|
29
|
+
video : {
|
|
30
|
+
mp4 : 'video/mp4'
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const allowedMimes = new Set(
|
|
35
|
+
Object.values( allowedUploadTypes ).flatMap(
|
|
36
|
+
( extensions ) => Object.values( extensions )
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const resolveUploadType = ( type, extension ) => {
|
|
41
|
+
|
|
42
|
+
const mime = allowedUploadTypes?.[ type ]?.[ extension ];
|
|
43
|
+
|
|
44
|
+
if( ! mime ){
|
|
45
|
+
|
|
46
|
+
const error = new Error( 'Unsupported upload type or extension' );
|
|
47
|
+
error.status = 400;
|
|
48
|
+
|
|
49
|
+
throw error;
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
return mime;
|
|
53
|
+
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const isAllowedMime = ( mime ) => allowedMimes.has( mime );
|
|
57
|
+
|
|
58
|
+
export { allowedMimes, allowedUploadTypes, isAllowedMime, resolveUploadType };
|
package/dist/upload.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Source-of-truth allowlist for file upload types accepted across drawbridge.
|
|
2
|
+
//
|
|
3
|
+
// Two consumer shapes:
|
|
4
|
+
//
|
|
5
|
+
// 1. The API needs `(type, extension) → mime` when serving an upload route
|
|
6
|
+
// where the URL carries those params. Use `resolveUploadType( type, extension )`.
|
|
7
|
+
// Throws { status: 400 } on anything not in the allowlist.
|
|
8
|
+
//
|
|
9
|
+
// 2. The sync worker downstream needs to validate that a `mimetype` flowing
|
|
10
|
+
// from the DB / job payload is one of the canonical values before passing
|
|
11
|
+
// it to S3 as a ContentType header. Use `isAllowedMime( mime )`.
|
|
12
|
+
//
|
|
13
|
+
// Adding a new accepted upload format means updating `allowedUploadTypes` here
|
|
14
|
+
// AND publishing a new utils version. Both api and sync inherit the change.
|
|
15
|
+
//
|
|
16
|
+
// Why both helpers and not just one: api routes know (type, extension) but not
|
|
17
|
+
// the canonical mime; sync workers have the mime but not the original type tuple.
|
|
18
|
+
|
|
19
|
+
const allowedUploadTypes = {
|
|
20
|
+
application : {
|
|
21
|
+
pdf : 'application/pdf'
|
|
22
|
+
},
|
|
23
|
+
image : {
|
|
24
|
+
jpeg : 'image/jpeg',
|
|
25
|
+
jpg : 'image/jpeg',
|
|
26
|
+
png : 'image/png',
|
|
27
|
+
webp : 'image/webp'
|
|
28
|
+
},
|
|
29
|
+
video : {
|
|
30
|
+
mp4 : 'video/mp4'
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const allowedMimes = new Set(
|
|
35
|
+
Object.values( allowedUploadTypes ).flatMap(
|
|
36
|
+
( extensions ) => Object.values( extensions )
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const resolveUploadType = ( type, extension ) => {
|
|
41
|
+
|
|
42
|
+
const mime = allowedUploadTypes?.[ type ]?.[ extension ];
|
|
43
|
+
|
|
44
|
+
if( ! mime ){
|
|
45
|
+
|
|
46
|
+
const error = new Error( 'Unsupported upload type or extension' );
|
|
47
|
+
error.status = 400;
|
|
48
|
+
|
|
49
|
+
throw error;
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
return mime;
|
|
53
|
+
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const isAllowedMime = ( mime ) => allowedMimes.has( mime );
|
|
57
|
+
|
|
58
|
+
export { allowedMimes, allowedUploadTypes, isAllowedMime, resolveUploadType };
|
package/dist/upload.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// upload.js
|
|
2
|
+
var allowedUploadTypes = {
|
|
3
|
+
application: {
|
|
4
|
+
pdf: "application/pdf"
|
|
5
|
+
},
|
|
6
|
+
image: {
|
|
7
|
+
jpeg: "image/jpeg",
|
|
8
|
+
jpg: "image/jpeg",
|
|
9
|
+
png: "image/png",
|
|
10
|
+
webp: "image/webp"
|
|
11
|
+
},
|
|
12
|
+
video: {
|
|
13
|
+
mp4: "video/mp4"
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var allowedMimes = new Set(
|
|
17
|
+
Object.values(allowedUploadTypes).flatMap(
|
|
18
|
+
(extensions) => Object.values(extensions)
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
var resolveUploadType = (type, extension) => {
|
|
22
|
+
var _a;
|
|
23
|
+
const mime = (_a = allowedUploadTypes == null ? void 0 : allowedUploadTypes[type]) == null ? void 0 : _a[extension];
|
|
24
|
+
if (!mime) {
|
|
25
|
+
const error = new Error("Unsupported upload type or extension");
|
|
26
|
+
error.status = 400;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
;
|
|
30
|
+
return mime;
|
|
31
|
+
};
|
|
32
|
+
var isAllowedMime = (mime) => allowedMimes.has(mime);
|
|
33
|
+
export {
|
|
34
|
+
allowedMimes,
|
|
35
|
+
allowedUploadTypes,
|
|
36
|
+
isAllowedMime,
|
|
37
|
+
resolveUploadType
|
|
38
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"dependencies": {
|
|
4
|
+
"@kinde/jwt-decoder": "0.2.1",
|
|
5
|
+
"@kinde/jwt-validator": "0.4.1",
|
|
6
|
+
"axios": "1.16.0",
|
|
4
7
|
"currency-codes": "2.2.0",
|
|
5
8
|
"nanoid": "3.3.8",
|
|
6
9
|
"tsup": "8.5.1",
|
|
@@ -16,6 +19,26 @@
|
|
|
16
19
|
"types": "./dist/encrypt.d.ts",
|
|
17
20
|
"import": "./dist/encrypt.js",
|
|
18
21
|
"require": "./dist/encrypt.cjs"
|
|
22
|
+
},
|
|
23
|
+
"./kinde": {
|
|
24
|
+
"types": "./dist/kinde.d.ts",
|
|
25
|
+
"import": "./dist/kinde.js",
|
|
26
|
+
"require": "./dist/kinde.cjs"
|
|
27
|
+
},
|
|
28
|
+
"./axios": {
|
|
29
|
+
"types": "./dist/axios.d.ts",
|
|
30
|
+
"import": "./dist/axios.js",
|
|
31
|
+
"require": "./dist/axios.cjs"
|
|
32
|
+
},
|
|
33
|
+
"./circuit": {
|
|
34
|
+
"types": "./dist/circuit.d.ts",
|
|
35
|
+
"import": "./dist/circuit.js",
|
|
36
|
+
"require": "./dist/circuit.cjs"
|
|
37
|
+
},
|
|
38
|
+
"./upload": {
|
|
39
|
+
"types": "./dist/upload.d.ts",
|
|
40
|
+
"import": "./dist/upload.js",
|
|
41
|
+
"require": "./dist/upload.cjs"
|
|
19
42
|
}
|
|
20
43
|
},
|
|
21
44
|
"files": [
|
|
@@ -32,5 +55,5 @@
|
|
|
32
55
|
"build": "tsup && npm publish"
|
|
33
56
|
},
|
|
34
57
|
"types": "dist/index.d.ts",
|
|
35
|
-
"version": "0.0.
|
|
58
|
+
"version": "0.0.7"
|
|
36
59
|
}
|