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