@eduzz/miau-client 1.0.2 → 1.0.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/.turbo/turbo-build$colon$types.log +1 -1
- package/dist/MiauClient.d.ts +3 -4
- package/dist/functions.d.ts +3 -0
- package/dist/functions.test.d.ts +1 -0
- package/dist/index.js +63 -38
- package/dist/index.js.map +3 -3
- package/dist/middleware.d.ts +1 -1
- package/package.json +3 -2
- package/scripts/should-release.sh +11 -1
- package/src/MiauClient.ts +38 -34
- package/src/functions.test.ts +76 -0
- package/src/functions.ts +38 -0
- package/src/middleware.ts +15 -15
package/dist/MiauClient.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type RequestHandler } from 'express';
|
|
2
|
-
import { type SecretEnv, type Permission } from '@eduzz/miau-types';
|
|
2
|
+
import { type SecretEnv, type Permission, type Resource } from '@eduzz/miau-types';
|
|
3
3
|
import { type RequestAugmentation } from './middleware';
|
|
4
4
|
type MiauClientConfig = {
|
|
5
5
|
apiUrl: string;
|
|
@@ -11,8 +11,6 @@ export declare class MiauClient {
|
|
|
11
11
|
private jwtToken;
|
|
12
12
|
private jwksClient;
|
|
13
13
|
private basicAuthToken;
|
|
14
|
-
private permissionsCache;
|
|
15
|
-
private permissionsRequests;
|
|
16
14
|
constructor(config: MiauClientConfig);
|
|
17
15
|
getEnvironment(): SecretEnv;
|
|
18
16
|
getPublicKey(kid: string): Promise<string>;
|
|
@@ -21,10 +19,11 @@ export declare class MiauClient {
|
|
|
21
19
|
requestAugmentation?: RequestAugmentation<T>;
|
|
22
20
|
fallbackMiddleware?: RequestHandler;
|
|
23
21
|
}): RequestHandler;
|
|
22
|
+
getResources(): Promise<Resource[]>;
|
|
24
23
|
getPermissions(targetAppId: string): Promise<Permission>;
|
|
25
|
-
private requestPermissions;
|
|
26
24
|
private getApiJwtUrl;
|
|
27
25
|
private getPermissionsUrl;
|
|
26
|
+
private getResourcesUrl;
|
|
28
27
|
private getJwksUrl;
|
|
29
28
|
}
|
|
30
29
|
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -11870,6 +11870,30 @@ var import_jwks_rsa = __toESM(require_src2());
|
|
|
11870
11870
|
|
|
11871
11871
|
// src/middleware.ts
|
|
11872
11872
|
var import_jsonwebtoken = __toESM(require_jsonwebtoken());
|
|
11873
|
+
|
|
11874
|
+
// src/functions.ts
|
|
11875
|
+
var wildcardToRegex = (pattern) => {
|
|
11876
|
+
const escaped = pattern.replace(/[-/\\^$+?.()|[\]{}]/g, "\\$&");
|
|
11877
|
+
const withWildcards = escaped.replace(/\*/g, "[^/]+");
|
|
11878
|
+
const regexStr = `^${withWildcards}$`;
|
|
11879
|
+
return new RegExp(regexStr);
|
|
11880
|
+
};
|
|
11881
|
+
var isResourceAllowed = (resource, resources, permittedResources) => {
|
|
11882
|
+
const hasExactMatch = resources.some((res) => {
|
|
11883
|
+
return res.method.toLowerCase() === resource.method.toLowerCase() && res.path.toLowerCase() === resource.path.toLowerCase();
|
|
11884
|
+
});
|
|
11885
|
+
if (hasExactMatch) {
|
|
11886
|
+
return permittedResources.some((perm) => {
|
|
11887
|
+
return perm.method.toLowerCase() === resource.method.toLowerCase() && perm.path.toLowerCase() === resource.path.toLowerCase();
|
|
11888
|
+
});
|
|
11889
|
+
}
|
|
11890
|
+
return permittedResources.some((permission) => {
|
|
11891
|
+
const permissionRegex = wildcardToRegex(permission.path);
|
|
11892
|
+
return permission.method.toLowerCase() === resource.method.toLowerCase() && permissionRegex.test(resource.path.toLowerCase());
|
|
11893
|
+
});
|
|
11894
|
+
};
|
|
11895
|
+
|
|
11896
|
+
// src/middleware.ts
|
|
11873
11897
|
var HttpError = class _HttpError extends Error {
|
|
11874
11898
|
constructor(status, name, message) {
|
|
11875
11899
|
super(message);
|
|
@@ -11878,12 +11902,6 @@ var HttpError = class _HttpError extends Error {
|
|
|
11878
11902
|
Object.setPrototypeOf(this, _HttpError.prototype);
|
|
11879
11903
|
}
|
|
11880
11904
|
};
|
|
11881
|
-
var wildcardToRegex = (pattern) => {
|
|
11882
|
-
const escaped = pattern.replace(/[-/\\^$+?.()|[\]{}]/g, "\\$&");
|
|
11883
|
-
const withWildcards = escaped.replace(/\*/g, "[^/]+");
|
|
11884
|
-
const regexStr = `^${withWildcards}$`;
|
|
11885
|
-
return new RegExp(regexStr);
|
|
11886
|
-
};
|
|
11887
11905
|
var miauMiddleware = (miauClient, requestAugmentation, fallbackMiddleware) => {
|
|
11888
11906
|
return async (req, res, next) => {
|
|
11889
11907
|
try {
|
|
@@ -11908,14 +11926,20 @@ var miauMiddleware = (miauClient, requestAugmentation, fallbackMiddleware) => {
|
|
|
11908
11926
|
`Secret environment ${secret.environment} does not match client environment ${miauClient.getEnvironment()}`
|
|
11909
11927
|
);
|
|
11910
11928
|
}
|
|
11929
|
+
const resources = await miauClient.getResources();
|
|
11930
|
+
if (!resources) {
|
|
11931
|
+
throw new HttpError(401, "Unauthorized", "No resources found for this application");
|
|
11932
|
+
}
|
|
11911
11933
|
const permission = await miauClient.getPermissions(application.id);
|
|
11912
11934
|
if (!permission) {
|
|
11913
11935
|
throw new HttpError(401, "Unauthorized", "No permissions found for this application");
|
|
11914
11936
|
}
|
|
11915
|
-
const
|
|
11916
|
-
|
|
11917
|
-
|
|
11918
|
-
}
|
|
11937
|
+
const permittedResources = permission?.resources || [];
|
|
11938
|
+
if (!permittedResources.length) {
|
|
11939
|
+
throw new HttpError(403, "Forbidden", "No resources are permitted for this application");
|
|
11940
|
+
}
|
|
11941
|
+
const resource = { protocol: "http", method: req.method, path: req.path };
|
|
11942
|
+
const isAllowed = isResourceAllowed(resource, resources, permittedResources);
|
|
11919
11943
|
if (!isAllowed) {
|
|
11920
11944
|
throw new HttpError(403, "Forbidden", `You do not have permission to access ${req.method} ${req.path}`);
|
|
11921
11945
|
}
|
|
@@ -11938,6 +11962,7 @@ var miauMiddleware = (miauClient, requestAugmentation, fallbackMiddleware) => {
|
|
|
11938
11962
|
};
|
|
11939
11963
|
|
|
11940
11964
|
// src/MiauClient.ts
|
|
11965
|
+
var requestsCache = /* @__PURE__ */ new Map();
|
|
11941
11966
|
var reusableFetch = async (input, init) => {
|
|
11942
11967
|
return new Promise(async (resolve, reject) => {
|
|
11943
11968
|
try {
|
|
@@ -11950,16 +11975,36 @@ var reusableFetch = async (input, init) => {
|
|
|
11950
11975
|
}
|
|
11951
11976
|
});
|
|
11952
11977
|
};
|
|
11978
|
+
var cacheableFetch = async (input, init) => {
|
|
11979
|
+
const cacheKeyData = JSON.stringify({ input, init });
|
|
11980
|
+
const cacheKey = import_node_crypto.default.createHash("sha256").update(cacheKeyData).digest("hex");
|
|
11981
|
+
const cachedResponse = requestsCache.get(cacheKey);
|
|
11982
|
+
if (cachedResponse) {
|
|
11983
|
+
const { data, headers } = await cachedResponse;
|
|
11984
|
+
const expiresAt = expiresHeaderToUnixtime(headers.get("Expires"));
|
|
11985
|
+
if (expiresAt > Date.now()) {
|
|
11986
|
+
return data;
|
|
11987
|
+
}
|
|
11988
|
+
requestsCache.delete(cacheKey);
|
|
11989
|
+
}
|
|
11990
|
+
const newRequest = reusableFetch(input, init);
|
|
11991
|
+
requestsCache.set(cacheKey, newRequest);
|
|
11992
|
+
return (await newRequest).data;
|
|
11993
|
+
};
|
|
11994
|
+
var expiresHeaderToUnixtime = (expires) => {
|
|
11995
|
+
return expires ? new Date(expires).getTime() : Date.now() + 6e4;
|
|
11996
|
+
};
|
|
11953
11997
|
var MiauClient = class {
|
|
11954
11998
|
constructor(config) {
|
|
11955
|
-
this.permissionsCache = /* @__PURE__ */ new Map();
|
|
11956
|
-
this.permissionsRequests = /* @__PURE__ */ new Map();
|
|
11957
11999
|
this.getApiJwtUrl = () => {
|
|
11958
12000
|
return `${this.config.apiUrl}/v1/jwt`;
|
|
11959
12001
|
};
|
|
11960
12002
|
this.getPermissionsUrl = () => {
|
|
11961
12003
|
return `${this.config.apiUrl}/v1/permissions`;
|
|
11962
12004
|
};
|
|
12005
|
+
this.getResourcesUrl = () => {
|
|
12006
|
+
return `${this.config.apiUrl}/v1/resources`;
|
|
12007
|
+
};
|
|
11963
12008
|
this.getJwksUrl = () => {
|
|
11964
12009
|
return `${this.config.apiUrl}/v1/jwks.json`;
|
|
11965
12010
|
};
|
|
@@ -12008,35 +12053,15 @@ var MiauClient = class {
|
|
|
12008
12053
|
middleware(config) {
|
|
12009
12054
|
return miauMiddleware(this, config?.requestAugmentation, config?.fallbackMiddleware);
|
|
12010
12055
|
}
|
|
12011
|
-
async
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
this.permissionsRequests.delete(targetAppId);
|
|
12016
|
-
return data;
|
|
12017
|
-
}
|
|
12018
|
-
}
|
|
12019
|
-
const request = await this.requestPermissions(targetAppId);
|
|
12020
|
-
if (request !== void 0) {
|
|
12021
|
-
const { data, headers } = request;
|
|
12022
|
-
this.permissionsCache.set(targetAppId, {
|
|
12023
|
-
data,
|
|
12024
|
-
expiresAt: headers.has("Expires") ? new Date(headers.get("Expires")).getTime() : Date.now() + 6e4
|
|
12025
|
-
// Default to 1 minute if no Expires header
|
|
12026
|
-
});
|
|
12027
|
-
return data;
|
|
12028
|
-
}
|
|
12029
|
-
throw new Error(`Failed to fetch permissions for app Id: ${targetAppId}`);
|
|
12056
|
+
async getResources() {
|
|
12057
|
+
return cacheableFetch(this.getResourcesUrl(), {
|
|
12058
|
+
headers: { Authorization: `Basic ${this.basicAuthToken}` }
|
|
12059
|
+
});
|
|
12030
12060
|
}
|
|
12031
|
-
async
|
|
12032
|
-
|
|
12033
|
-
return this.permissionsRequests.get(sourceAppId);
|
|
12034
|
-
}
|
|
12035
|
-
const request = reusableFetch(`${this.getPermissionsUrl()}/${sourceAppId}`, {
|
|
12061
|
+
async getPermissions(targetAppId) {
|
|
12062
|
+
return cacheableFetch(`${this.getPermissionsUrl()}/${targetAppId}`, {
|
|
12036
12063
|
headers: { Authorization: `Basic ${this.basicAuthToken}` }
|
|
12037
12064
|
});
|
|
12038
|
-
this.permissionsRequests.set(sourceAppId, request);
|
|
12039
|
-
return request;
|
|
12040
12065
|
}
|
|
12041
12066
|
};
|
|
12042
12067
|
// Annotate the CommonJS export names for ESM import in node:
|