@eldrin-project/eldrin-app-core 0.0.2 → 0.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/dist/index.cjs +208 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +240 -1
- package/dist/index.d.ts +240 -1
- package/dist/index.js +205 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -957,14 +957,220 @@ function requireAllPermissions(request, permissions) {
|
|
|
957
957
|
return auth;
|
|
958
958
|
}
|
|
959
959
|
|
|
960
|
+
// src/middleware/matcher.ts
|
|
961
|
+
function compileRoutes(routes, apiPrefix) {
|
|
962
|
+
return routes.map((route) => {
|
|
963
|
+
const methods = new Set(
|
|
964
|
+
Array.isArray(route.method) ? route.method.map((m) => m.toUpperCase()) : [route.method.toUpperCase()]
|
|
965
|
+
);
|
|
966
|
+
const fullPath = `${apiPrefix}${route.path}`;
|
|
967
|
+
const paramNames = [];
|
|
968
|
+
let regexPattern = fullPath.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name) => {
|
|
969
|
+
paramNames.push(name);
|
|
970
|
+
return "([^/]+)";
|
|
971
|
+
}).replace(/\*/g, "[^/]+");
|
|
972
|
+
const pattern = new RegExp(`^${regexPattern}$`);
|
|
973
|
+
let permission = null;
|
|
974
|
+
if (route.permission) {
|
|
975
|
+
const colonIndex = route.permission.indexOf(":");
|
|
976
|
+
if (colonIndex > 0) {
|
|
977
|
+
permission = {
|
|
978
|
+
resource: route.permission.substring(0, colonIndex),
|
|
979
|
+
action: route.permission.substring(colonIndex + 1)
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return {
|
|
984
|
+
methods,
|
|
985
|
+
pattern,
|
|
986
|
+
paramNames,
|
|
987
|
+
permission,
|
|
988
|
+
originalPath: route.path
|
|
989
|
+
};
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
function matchRoute(method, pathname, compiledRoutes) {
|
|
993
|
+
const upperMethod = method.toUpperCase();
|
|
994
|
+
for (const route of compiledRoutes) {
|
|
995
|
+
if (!route.methods.has(upperMethod)) continue;
|
|
996
|
+
const match = pathname.match(route.pattern);
|
|
997
|
+
if (match) {
|
|
998
|
+
const params = {};
|
|
999
|
+
route.paramNames.forEach((name, index) => {
|
|
1000
|
+
params[name] = match[index + 1];
|
|
1001
|
+
});
|
|
1002
|
+
return { route, params };
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
function isPublicRoute(pathname, publicRoutes, apiPrefix) {
|
|
1008
|
+
for (const publicPath of publicRoutes) {
|
|
1009
|
+
const fullPath = `${apiPrefix}${publicPath}`;
|
|
1010
|
+
if (pathname === fullPath) return true;
|
|
1011
|
+
if (publicPath.endsWith("/*")) {
|
|
1012
|
+
const prefix = `${apiPrefix}${publicPath.slice(0, -2)}`;
|
|
1013
|
+
if (pathname.startsWith(prefix + "/")) return true;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/middleware/index.ts
|
|
1020
|
+
function createPermissionMiddleware(config) {
|
|
1021
|
+
const apiPrefix = config.apiPrefix ?? "/api";
|
|
1022
|
+
const manifest = config.manifest;
|
|
1023
|
+
const api = manifest.api;
|
|
1024
|
+
const compiledRoutes = api?.routes ? compileRoutes(api.routes, apiPrefix) : [];
|
|
1025
|
+
const defaultPolicy = api?.defaultPolicy ?? "deny";
|
|
1026
|
+
const publicRoutes = api?.publicRoutes ?? [];
|
|
1027
|
+
const emptyAuth = {
|
|
1028
|
+
userId: "",
|
|
1029
|
+
email: "",
|
|
1030
|
+
name: "",
|
|
1031
|
+
platformRoles: [],
|
|
1032
|
+
permissions: []
|
|
1033
|
+
};
|
|
1034
|
+
function createCorsHeaders() {
|
|
1035
|
+
if (!config.cors) return {};
|
|
1036
|
+
return {
|
|
1037
|
+
"Access-Control-Allow-Origin": config.cors.allowOrigin,
|
|
1038
|
+
"Access-Control-Allow-Methods": config.cors.allowMethods,
|
|
1039
|
+
"Access-Control-Allow-Headers": config.cors.allowHeaders
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function withCors(response) {
|
|
1043
|
+
if (!config.cors) return response;
|
|
1044
|
+
const newHeaders = new Headers(response.headers);
|
|
1045
|
+
Object.entries(createCorsHeaders()).forEach(([key, value]) => {
|
|
1046
|
+
newHeaders.set(key, value);
|
|
1047
|
+
});
|
|
1048
|
+
return new Response(response.body, {
|
|
1049
|
+
status: response.status,
|
|
1050
|
+
statusText: response.statusText,
|
|
1051
|
+
headers: newHeaders
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
function errorResponse(error) {
|
|
1055
|
+
if (config.onError) {
|
|
1056
|
+
return withCors(config.onError(error));
|
|
1057
|
+
}
|
|
1058
|
+
switch (error.type) {
|
|
1059
|
+
case "unauthorized":
|
|
1060
|
+
return withCors(
|
|
1061
|
+
Response.json(
|
|
1062
|
+
{ error: "Unauthorized", message: error.message },
|
|
1063
|
+
{ status: 401 }
|
|
1064
|
+
)
|
|
1065
|
+
);
|
|
1066
|
+
case "forbidden":
|
|
1067
|
+
return withCors(
|
|
1068
|
+
Response.json(
|
|
1069
|
+
{
|
|
1070
|
+
error: "Forbidden",
|
|
1071
|
+
message: error.message,
|
|
1072
|
+
permission: error.permission
|
|
1073
|
+
},
|
|
1074
|
+
{ status: 403 }
|
|
1075
|
+
)
|
|
1076
|
+
);
|
|
1077
|
+
case "route_not_found":
|
|
1078
|
+
return withCors(
|
|
1079
|
+
Response.json(
|
|
1080
|
+
{ error: "Not Found", message: error.message },
|
|
1081
|
+
{ status: 404 }
|
|
1082
|
+
)
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
async function verifyAndGetAuth(request, env) {
|
|
1087
|
+
const jwtOptions = {
|
|
1088
|
+
secret: config.getSecret(env),
|
|
1089
|
+
appId: manifest.id,
|
|
1090
|
+
developerId: manifest.developer_id
|
|
1091
|
+
};
|
|
1092
|
+
const result = await verifyJWT(request, jwtOptions);
|
|
1093
|
+
if (!result.success) {
|
|
1094
|
+
return errorResponse({
|
|
1095
|
+
type: "unauthorized",
|
|
1096
|
+
message: result.error
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
return result.auth;
|
|
1100
|
+
}
|
|
1101
|
+
return {
|
|
1102
|
+
async handle(request, env) {
|
|
1103
|
+
const url = new URL(request.url);
|
|
1104
|
+
const method = request.method;
|
|
1105
|
+
const pathname = url.pathname;
|
|
1106
|
+
if (config.skipRoutes) {
|
|
1107
|
+
for (const skipPattern of config.skipRoutes) {
|
|
1108
|
+
if (pathname.startsWith(skipPattern)) {
|
|
1109
|
+
return { auth: emptyAuth, url, params: {} };
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (method === "OPTIONS") {
|
|
1114
|
+
return {
|
|
1115
|
+
response: new Response(null, {
|
|
1116
|
+
status: 204,
|
|
1117
|
+
headers: createCorsHeaders()
|
|
1118
|
+
})
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
if (!pathname.startsWith(apiPrefix)) {
|
|
1122
|
+
return { auth: emptyAuth, url, params: {} };
|
|
1123
|
+
}
|
|
1124
|
+
if (isPublicRoute(pathname, publicRoutes, apiPrefix)) {
|
|
1125
|
+
return { auth: emptyAuth, url, params: {} };
|
|
1126
|
+
}
|
|
1127
|
+
const match = matchRoute(method, pathname, compiledRoutes);
|
|
1128
|
+
if (!match) {
|
|
1129
|
+
if (defaultPolicy === "allow") {
|
|
1130
|
+
return { auth: emptyAuth, url, params: {} };
|
|
1131
|
+
}
|
|
1132
|
+
const authResult2 = await verifyAndGetAuth(request, env);
|
|
1133
|
+
if (authResult2 instanceof Response) {
|
|
1134
|
+
return { response: authResult2 };
|
|
1135
|
+
}
|
|
1136
|
+
return { auth: authResult2, url, params: {} };
|
|
1137
|
+
}
|
|
1138
|
+
if (match.route.permission === null) {
|
|
1139
|
+
return { auth: emptyAuth, url, params: match.params };
|
|
1140
|
+
}
|
|
1141
|
+
const authResult = await verifyAndGetAuth(request, env);
|
|
1142
|
+
if (authResult instanceof Response) {
|
|
1143
|
+
return { response: authResult };
|
|
1144
|
+
}
|
|
1145
|
+
const auth = authResult;
|
|
1146
|
+
if (isPlatformAdmin(auth)) {
|
|
1147
|
+
return { auth, url, params: match.params };
|
|
1148
|
+
}
|
|
1149
|
+
const { resource, action } = match.route.permission;
|
|
1150
|
+
if (!hasPermission(auth, resource, action)) {
|
|
1151
|
+
return {
|
|
1152
|
+
response: errorResponse({
|
|
1153
|
+
type: "forbidden",
|
|
1154
|
+
message: `Missing permission: ${resource}:${action}`,
|
|
1155
|
+
permission: `${resource}:${action}`
|
|
1156
|
+
})
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
return { auth, url, params: match.params };
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
|
|
960
1164
|
exports.AUTH_HEADERS = AUTH_HEADERS;
|
|
961
1165
|
exports.CHECKSUM_PREFIX = CHECKSUM_PREFIX;
|
|
962
1166
|
exports.DatabaseProvider = DatabaseProvider;
|
|
963
1167
|
exports.EldrinEventClient = EldrinEventClient;
|
|
964
1168
|
exports.calculateChecksum = calculateChecksum;
|
|
965
1169
|
exports.calculatePrefixedChecksum = calculatePrefixedChecksum;
|
|
1170
|
+
exports.compileRoutes = compileRoutes;
|
|
966
1171
|
exports.createApp = createApp;
|
|
967
1172
|
exports.createEventClient = createEventClient;
|
|
1173
|
+
exports.createPermissionMiddleware = createPermissionMiddleware;
|
|
968
1174
|
exports.extractTimestamp = extractTimestamp;
|
|
969
1175
|
exports.generateMigrationManifest = generateMigrationManifest;
|
|
970
1176
|
exports.getAuthContext = getAuthContext;
|
|
@@ -974,8 +1180,10 @@ exports.getRollbackFilename = getRollbackFilename;
|
|
|
974
1180
|
exports.hasPermission = hasPermission;
|
|
975
1181
|
exports.hasPlatformRole = hasPlatformRole;
|
|
976
1182
|
exports.isPlatformAdmin = isPlatformAdmin;
|
|
1183
|
+
exports.isPublicRoute = isPublicRoute;
|
|
977
1184
|
exports.isValidMigrationFilename = isValidMigrationFilename;
|
|
978
1185
|
exports.isValidRollbackFilename = isValidRollbackFilename;
|
|
1186
|
+
exports.matchRoute = matchRoute;
|
|
979
1187
|
exports.parseSQLStatements = parseSQLStatements;
|
|
980
1188
|
exports.requireAllPermissions = requireAllPermissions;
|
|
981
1189
|
exports.requireAnyPermission = requireAnyPermission;
|