@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.js CHANGED
@@ -955,6 +955,210 @@ function requireAllPermissions(request, permissions) {
955
955
  return auth;
956
956
  }
957
957
 
958
- export { AUTH_HEADERS, CHECKSUM_PREFIX, DatabaseProvider, EldrinEventClient, calculateChecksum, calculatePrefixedChecksum, createApp, createEventClient, extractTimestamp, generateMigrationManifest, getAuthContext, getAuthContextFromJWT, getMigrationStatus, getRollbackFilename, hasPermission, hasPlatformRole, isPlatformAdmin, isValidMigrationFilename, isValidRollbackFilename, parseSQLStatements, requireAllPermissions, requireAnyPermission, requireAuth, requireJWTAuth, requireJWTPermission, requirePermission, rollbackMigrations, runMigrations, useDatabase, useDatabaseContext, useMigrationsComplete, validateMigrationManifest, verifyChecksum, verifyJWT };
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