@rebasepro/server-core 0.0.1-canary.4d4fb3e → 0.0.1-canary.ca2cb6e
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/common/src/collections/CollectionRegistry.d.ts +8 -0
- package/dist/common/src/util/entities.d.ts +22 -0
- package/dist/common/src/util/relations.d.ts +14 -4
- package/dist/common/src/util/resolutions.d.ts +1 -1
- package/dist/index-DXVBFp5V.js +37 -0
- package/dist/index-DXVBFp5V.js.map +1 -0
- package/dist/index.es.js +49931 -55
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +28475 -34569
- package/dist/index.umd.js.map +1 -1
- package/dist/server-core/src/api/openapi-generator.d.ts +15 -1
- package/dist/server-core/src/api/types.d.ts +1 -1
- package/dist/server-core/src/auth/admin-routes.d.ts +10 -1
- package/dist/server-core/src/auth/apple-oauth.d.ts +30 -0
- package/dist/server-core/src/auth/bitbucket-oauth.d.ts +11 -0
- package/dist/server-core/src/auth/discord-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/facebook-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/github-oauth.d.ts +15 -0
- package/dist/server-core/src/auth/gitlab-oauth.d.ts +13 -0
- package/dist/server-core/src/auth/google-oauth.d.ts +5 -11
- package/dist/server-core/src/auth/index.d.ts +13 -2
- package/dist/server-core/src/auth/interfaces.d.ts +45 -6
- package/dist/server-core/src/auth/jwt.d.ts +1 -0
- package/dist/server-core/src/auth/linkedin-oauth.d.ts +18 -0
- package/dist/server-core/src/auth/microsoft-oauth.d.ts +16 -0
- package/dist/server-core/src/auth/middleware.d.ts +36 -11
- package/dist/server-core/src/auth/rate-limiter.d.ts +2 -2
- package/dist/server-core/src/auth/routes.d.ts +13 -3
- package/dist/server-core/src/auth/slack-oauth.d.ts +12 -0
- package/dist/server-core/src/auth/spotify-oauth.d.ts +12 -0
- package/dist/server-core/src/auth/twitter-oauth.d.ts +18 -0
- package/dist/server-core/src/cron/cron-loader.d.ts +17 -0
- package/dist/server-core/src/cron/cron-routes.d.ts +14 -0
- package/dist/server-core/src/cron/cron-scheduler.d.ts +61 -0
- package/dist/server-core/src/cron/cron-store.d.ts +32 -0
- package/dist/server-core/src/cron/index.d.ts +6 -0
- package/dist/server-core/src/email/index.d.ts +2 -2
- package/dist/server-core/src/email/templates.d.ts +9 -0
- package/dist/server-core/src/email/types.d.ts +20 -23
- package/dist/server-core/src/history/history-routes.d.ts +2 -2
- package/dist/server-core/src/index.d.ts +5 -0
- package/dist/server-core/src/init.d.ts +112 -2
- package/dist/server-core/src/singleton.d.ts +35 -0
- package/dist/server-core/src/storage/LocalStorageController.d.ts +9 -9
- package/dist/server-core/src/storage/S3StorageController.d.ts +8 -8
- package/dist/server-core/src/storage/index.d.ts +18 -11
- package/dist/server-core/src/storage/routes.d.ts +3 -3
- package/dist/server-core/src/storage/types.d.ts +28 -16
- package/dist/server-core/src/utils/dev-port.d.ts +35 -0
- package/dist/server-core/src/utils/logger.d.ts +31 -0
- package/dist/server-core/src/utils/request-logger.d.ts +19 -0
- package/dist/types/src/controllers/auth.d.ts +2 -0
- package/dist/types/src/controllers/client.d.ts +119 -7
- package/dist/types/src/controllers/collection_registry.d.ts +4 -3
- package/dist/types/src/controllers/customization_controller.d.ts +7 -1
- package/dist/types/src/controllers/data.d.ts +34 -7
- package/dist/types/src/controllers/data_driver.d.ts +20 -28
- package/dist/types/src/controllers/database_admin.d.ts +2 -2
- package/dist/types/src/controllers/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +1 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +4 -4
- package/dist/types/src/controllers/navigation.d.ts +5 -5
- package/dist/types/src/controllers/registry.d.ts +6 -3
- package/dist/types/src/controllers/side_entity_controller.d.ts +7 -6
- package/dist/types/src/controllers/storage.d.ts +24 -26
- package/dist/types/src/rebase_context.d.ts +8 -4
- package/dist/types/src/types/backend.d.ts +4 -1
- package/dist/types/src/types/builders.d.ts +5 -4
- package/dist/types/src/types/chips.d.ts +1 -1
- package/dist/types/src/types/collections.d.ts +169 -125
- package/dist/types/src/types/cron.d.ts +102 -0
- package/dist/types/src/types/data_source.d.ts +1 -1
- package/dist/types/src/types/entity_actions.d.ts +8 -8
- package/dist/types/src/types/entity_callbacks.d.ts +15 -15
- package/dist/types/src/types/entity_link_builder.d.ts +1 -1
- package/dist/types/src/types/entity_overrides.d.ts +2 -1
- package/dist/types/src/types/entity_views.d.ts +8 -8
- package/dist/types/src/types/export_import.d.ts +3 -3
- package/dist/types/src/types/index.d.ts +1 -0
- package/dist/types/src/types/plugins.d.ts +72 -18
- package/dist/types/src/types/properties.d.ts +118 -33
- package/dist/types/src/types/relations.d.ts +1 -1
- package/dist/types/src/types/slots.d.ts +30 -6
- package/dist/types/src/types/translations.d.ts +44 -0
- package/dist/types/src/types/user_management_delegate.d.ts +1 -0
- package/package.json +84 -84
- package/scratch.ts +6 -5
- package/src/api/ast-schema-editor.ts +11 -11
- package/src/api/collections_for_test/callbacks_test_collection.ts +5 -2
- package/src/api/errors.ts +38 -14
- package/src/api/graphql/graphql-schema-generator.ts +5 -3
- package/src/api/openapi-generator.ts +613 -58
- package/src/api/rest/api-generator.ts +17 -11
- package/src/api/rest/query-parser.ts +1 -1
- package/src/api/schema-editor-routes.ts +2 -0
- package/src/api/server.ts +8 -5
- package/src/api/types.ts +1 -1
- package/src/auth/admin-routes.ts +58 -17
- package/src/auth/apple-oauth.ts +130 -0
- package/src/auth/bitbucket-oauth.ts +82 -0
- package/src/auth/discord-oauth.ts +83 -0
- package/src/auth/facebook-oauth.ts +72 -0
- package/src/auth/github-oauth.ts +110 -0
- package/src/auth/gitlab-oauth.ts +70 -0
- package/src/auth/google-oauth.ts +34 -46
- package/src/auth/index.ts +15 -2
- package/src/auth/interfaces.ts +54 -7
- package/src/auth/jwt.ts +22 -5
- package/src/auth/linkedin-oauth.ts +81 -0
- package/src/auth/microsoft-oauth.ts +88 -0
- package/src/auth/middleware.ts +194 -45
- package/src/auth/password.ts +3 -1
- package/src/auth/rate-limiter.ts +4 -4
- package/src/auth/routes.ts +197 -139
- package/src/auth/slack-oauth.ts +71 -0
- package/src/auth/spotify-oauth.ts +67 -0
- package/src/auth/twitter-oauth.ts +120 -0
- package/src/collections/loader.ts +6 -6
- package/src/cron/cron-loader.ts +89 -0
- package/src/cron/cron-routes.test.ts +265 -0
- package/src/cron/cron-routes.ts +85 -0
- package/src/cron/cron-scheduler.test.ts +421 -0
- package/src/cron/cron-scheduler.ts +413 -0
- package/src/cron/cron-store.ts +163 -0
- package/src/cron/index.ts +6 -0
- package/src/db/interfaces.ts +2 -2
- package/src/email/index.ts +3 -2
- package/src/email/smtp-email-service.ts +5 -2
- package/src/email/templates.ts +93 -6
- package/src/email/types.ts +17 -24
- package/src/functions/function-loader.ts +34 -6
- package/src/functions/function-routes.ts +2 -2
- package/src/history/history-routes.ts +4 -3
- package/src/index.ts +10 -0
- package/src/init.ts +475 -57
- package/src/services/driver-registry.ts +1 -1
- package/src/singleton.test.ts +28 -0
- package/src/singleton.ts +70 -0
- package/src/storage/LocalStorageController.ts +50 -53
- package/src/storage/S3StorageController.ts +49 -46
- package/src/storage/index.ts +27 -16
- package/src/storage/routes.ts +76 -59
- package/src/storage/storage-registry.ts +5 -5
- package/src/storage/types.ts +44 -32
- package/src/types/index.ts +4 -4
- package/src/utils/dev-port.ts +176 -0
- package/src/utils/logger.ts +143 -0
- package/src/utils/logging.ts +5 -2
- package/src/utils/request-logger.ts +66 -0
- package/test/admin-routes.test.ts +114 -65
- package/test/api-generator.test.ts +73 -30
- package/test/ast-schema-editor.test.ts +5 -3
- package/test/auth-middleware-hono.test.ts +268 -33
- package/test/auth-routes.test.ts +325 -146
- package/test/driver-registry.test.ts +4 -2
- package/test/error-propagation.test.ts +226 -0
- package/test/errors.test.ts +12 -7
- package/test/jwt-security.test.ts +18 -9
- package/test/jwt.test.ts +26 -13
- package/test/middleware.test.ts +13 -8
- package/test/query-parser.test.ts +13 -8
- package/test/rate-limiter.test.ts +7 -7
- package/test/safe-compare.test.ts +66 -0
- package/test/singleton.test.ts +59 -0
- package/test/storage-local.test.ts +36 -43
- package/test/storage-registry.test.ts +38 -36
- package/test/storage-routes.test.ts +9 -5
- package/test/storage-s3.test.ts +51 -48
- package/test.ts +6 -0
- package/tsconfig.json +1 -1
- package/vite.config.ts +7 -5
- package/dist/index-BeMqpmfQ.js +0 -239
- package/dist/index-BeMqpmfQ.js.map +0 -1
- package/dist/index-bl4J3lNb.js +0 -55823
- package/dist/index-bl4J3lNb.js.map +0 -1
- package/vite.config.ts.timestamp-1775065397568-8a853255edf6e.mjs +0 -46
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, jest } from "@jest/globals";
|
|
1
2
|
import { Hono, Context } from "hono";
|
|
2
|
-
import { configureJwt, generateAccessToken } from "../src/auth/jwt
|
|
3
|
-
import { requireAuth, optionalAuth, requireAdmin, createAuthMiddleware } from "../src/auth/middleware
|
|
4
|
-
import type { HonoEnv } from "../src/api/types
|
|
5
|
-
import type { DataDriver } from "../../types/src/controllers/data_driver
|
|
3
|
+
import { configureJwt, generateAccessToken } from "../src/auth/jwt";
|
|
4
|
+
import { requireAuth, optionalAuth, requireAdmin, createAuthMiddleware, createRequireAuth } from "../src/auth/middleware";
|
|
5
|
+
import type { HonoEnv } from "../src/api/types";
|
|
6
|
+
import type { DataDriver } from "../../types/src/controllers/data_driver";
|
|
6
7
|
|
|
7
8
|
const TEST_SECRET = "test-secret-key-for-hono-middleware-testing-1234567890";
|
|
8
9
|
|
|
9
10
|
describe("Auth Middleware (Hono)", () => {
|
|
10
11
|
|
|
11
12
|
beforeAll(() => {
|
|
12
|
-
configureJwt({ secret: TEST_SECRET,
|
|
13
|
+
configureJwt({ secret: TEST_SECRET,
|
|
14
|
+
accessExpiresIn: "1h" });
|
|
13
15
|
});
|
|
14
16
|
|
|
15
17
|
// ── requireAuth ─────────────────────────────────────────
|
|
@@ -28,7 +30,7 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
28
30
|
const app = createApp();
|
|
29
31
|
const token = generateAccessToken("user-1", ["admin"]);
|
|
30
32
|
const res = await app.request("/protected/resource", {
|
|
31
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
33
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
32
34
|
});
|
|
33
35
|
expect(res.status).toBe(200);
|
|
34
36
|
const body = await res.json() as any;
|
|
@@ -47,7 +49,7 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
47
49
|
it("returns 401 for non-Bearer prefix", async () => {
|
|
48
50
|
const app = createApp();
|
|
49
51
|
const res = await app.request("/protected/resource", {
|
|
50
|
-
headers: { Authorization: "Basic abc123" }
|
|
52
|
+
headers: { Authorization: "Basic abc123" }
|
|
51
53
|
});
|
|
52
54
|
expect(res.status).toBe(401);
|
|
53
55
|
});
|
|
@@ -55,7 +57,7 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
55
57
|
it("returns 401 for invalid/expired token", async () => {
|
|
56
58
|
const app = createApp();
|
|
57
59
|
const res = await app.request("/protected/resource", {
|
|
58
|
-
headers: { Authorization: "Bearer invalid.token.here" }
|
|
60
|
+
headers: { Authorization: "Bearer invalid.token.here" }
|
|
59
61
|
});
|
|
60
62
|
expect(res.status).toBe(401);
|
|
61
63
|
const body = await res.json() as any;
|
|
@@ -76,7 +78,7 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
76
78
|
const bearerToken = generateAccessToken("bearer-user", ["admin"]);
|
|
77
79
|
const queryToken = generateAccessToken("query-user", ["viewer"]);
|
|
78
80
|
const res = await app.request(`/protected/resource?token=${queryToken}`, {
|
|
79
|
-
headers: { Authorization: `Bearer ${bearerToken}` }
|
|
81
|
+
headers: { Authorization: `Bearer ${bearerToken}` }
|
|
80
82
|
});
|
|
81
83
|
expect(res.status).toBe(200);
|
|
82
84
|
const body = await res.json() as any;
|
|
@@ -97,7 +99,7 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
97
99
|
const app = createApp();
|
|
98
100
|
const token = generateAccessToken("admin-1", ["admin"]);
|
|
99
101
|
const res = await app.request("/admin/dashboard", {
|
|
100
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
102
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
101
103
|
});
|
|
102
104
|
expect(res.status).toBe(200);
|
|
103
105
|
});
|
|
@@ -106,7 +108,7 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
106
108
|
const app = createApp();
|
|
107
109
|
const token = generateAccessToken("schema-admin-1", ["schema-admin"]);
|
|
108
110
|
const res = await app.request("/admin/dashboard", {
|
|
109
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
111
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
110
112
|
});
|
|
111
113
|
expect(res.status).toBe(200);
|
|
112
114
|
});
|
|
@@ -115,7 +117,7 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
115
117
|
const app = createApp();
|
|
116
118
|
const token = generateAccessToken("user-1", ["editor"]);
|
|
117
119
|
const res = await app.request("/admin/dashboard", {
|
|
118
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
120
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
119
121
|
});
|
|
120
122
|
expect(res.status).toBe(403);
|
|
121
123
|
const body = await res.json() as any;
|
|
@@ -139,7 +141,8 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
139
141
|
app.use("/public/*", optionalAuth);
|
|
140
142
|
app.get("/public/feed", (c: Context<HonoEnv>) => {
|
|
141
143
|
const user = c.get("user");
|
|
142
|
-
return c.json({ authenticated: !!user,
|
|
144
|
+
return c.json({ authenticated: !!user,
|
|
145
|
+
user: user ?? null });
|
|
143
146
|
});
|
|
144
147
|
return app;
|
|
145
148
|
}
|
|
@@ -148,7 +151,7 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
148
151
|
const app = createApp();
|
|
149
152
|
const token = generateAccessToken("opt-user", ["viewer"]);
|
|
150
153
|
const res = await app.request("/public/feed", {
|
|
151
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
154
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
152
155
|
});
|
|
153
156
|
const body = await res.json() as any;
|
|
154
157
|
expect(res.status).toBe(200);
|
|
@@ -168,7 +171,7 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
168
171
|
it("proceeds without user when token is invalid", async () => {
|
|
169
172
|
const app = createApp();
|
|
170
173
|
const res = await app.request("/public/feed", {
|
|
171
|
-
headers: { Authorization: "Bearer garbage.token.value" }
|
|
174
|
+
headers: { Authorization: "Bearer garbage.token.value" }
|
|
172
175
|
});
|
|
173
176
|
const body = await res.json() as any;
|
|
174
177
|
expect(res.status).toBe(200);
|
|
@@ -182,12 +185,13 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
182
185
|
fetchCollection: jest.fn() as any,
|
|
183
186
|
fetchEntity: jest.fn() as any,
|
|
184
187
|
saveEntity: jest.fn() as any,
|
|
185
|
-
deleteEntity: jest.fn() as any
|
|
188
|
+
deleteEntity: jest.fn() as any
|
|
186
189
|
};
|
|
187
190
|
|
|
188
|
-
it("sets driver in context", async () => {
|
|
191
|
+
it("sets driver in context (requireAuth: false)", async () => {
|
|
189
192
|
const app = new Hono<HonoEnv>();
|
|
190
|
-
app.use("/*", createAuthMiddleware({ driver: mockDriver
|
|
193
|
+
app.use("/*", createAuthMiddleware({ driver: mockDriver,
|
|
194
|
+
requireAuth: false }));
|
|
191
195
|
app.get("/test", (c) => {
|
|
192
196
|
const driver = c.get("driver");
|
|
193
197
|
return c.json({ hasDriver: !!driver });
|
|
@@ -199,9 +203,19 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
199
203
|
expect(body.hasDriver).toBe(true);
|
|
200
204
|
});
|
|
201
205
|
|
|
206
|
+
it("enforces auth by default (requireAuth defaults to true)", async () => {
|
|
207
|
+
const app = new Hono<HonoEnv>();
|
|
208
|
+
app.use("/*", createAuthMiddleware({ driver: mockDriver }));
|
|
209
|
+
app.get("/test", (c) => c.json({ ok: true }));
|
|
210
|
+
|
|
211
|
+
const res = await app.request("/test");
|
|
212
|
+
expect(res.status).toBe(401);
|
|
213
|
+
});
|
|
214
|
+
|
|
202
215
|
it("enforces auth when requireAuth is true", async () => {
|
|
203
216
|
const app = new Hono<HonoEnv>();
|
|
204
|
-
app.use("/*", createAuthMiddleware({ driver: mockDriver,
|
|
217
|
+
app.use("/*", createAuthMiddleware({ driver: mockDriver,
|
|
218
|
+
requireAuth: true }));
|
|
205
219
|
app.get("/test", (c) => c.json({ ok: true }));
|
|
206
220
|
|
|
207
221
|
const res = await app.request("/test");
|
|
@@ -210,7 +224,8 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
210
224
|
|
|
211
225
|
it("allows anonymous when requireAuth is false", async () => {
|
|
212
226
|
const app = new Hono<HonoEnv>();
|
|
213
|
-
app.use("/*", createAuthMiddleware({ driver: mockDriver,
|
|
227
|
+
app.use("/*", createAuthMiddleware({ driver: mockDriver,
|
|
228
|
+
requireAuth: false }));
|
|
214
229
|
app.get("/test", (c) => c.json({ ok: true }));
|
|
215
230
|
|
|
216
231
|
const res = await app.request("/test");
|
|
@@ -219,6 +234,7 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
219
234
|
|
|
220
235
|
it("extracts JWT and sets user in context", async () => {
|
|
221
236
|
const app = new Hono<HonoEnv>();
|
|
237
|
+
// Default requireAuth=true, but providing a valid token satisfies it
|
|
222
238
|
app.use("/*", createAuthMiddleware({ driver: mockDriver }));
|
|
223
239
|
app.get("/test", (c) => {
|
|
224
240
|
const user = c.get("user");
|
|
@@ -227,13 +243,27 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
227
243
|
|
|
228
244
|
const token = generateAccessToken("jwt-user", ["admin"]);
|
|
229
245
|
const res = await app.request("/test", {
|
|
230
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
246
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
231
247
|
});
|
|
232
248
|
const body = await res.json() as any;
|
|
233
249
|
expect(body.user.userId).toBe("jwt-user");
|
|
234
250
|
expect(body.user.roles).toEqual(["admin"]);
|
|
235
251
|
});
|
|
236
252
|
|
|
253
|
+
it("rejects invalid tokens even with requireAuth: false (fail closed)", async () => {
|
|
254
|
+
const app = new Hono<HonoEnv>();
|
|
255
|
+
app.use("/*", createAuthMiddleware({ driver: mockDriver,
|
|
256
|
+
requireAuth: false }));
|
|
257
|
+
app.get("/test", (c) => c.json({ ok: true }));
|
|
258
|
+
|
|
259
|
+
const res = await app.request("/test", {
|
|
260
|
+
headers: { Authorization: "Bearer invalid.garbage.token" }
|
|
261
|
+
});
|
|
262
|
+
expect(res.status).toBe(401);
|
|
263
|
+
const body = await res.json() as any;
|
|
264
|
+
expect(body.error.code).toBe("UNAUTHORIZED");
|
|
265
|
+
});
|
|
266
|
+
|
|
237
267
|
it("uses custom validator when provided", async () => {
|
|
238
268
|
const app = new Hono<HonoEnv>();
|
|
239
269
|
app.use("/*", createAuthMiddleware({
|
|
@@ -241,10 +271,11 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
241
271
|
validator: async (c: Context<HonoEnv>) => {
|
|
242
272
|
const apiKey = c.req.header("x-api-key");
|
|
243
273
|
if (apiKey === "valid-key") {
|
|
244
|
-
return { userId: "api-user",
|
|
274
|
+
return { userId: "api-user",
|
|
275
|
+
roles: ["api"] };
|
|
245
276
|
}
|
|
246
277
|
return false;
|
|
247
|
-
}
|
|
278
|
+
}
|
|
248
279
|
}));
|
|
249
280
|
app.get("/test", (c) => {
|
|
250
281
|
const user = c.get("user");
|
|
@@ -253,23 +284,24 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
253
284
|
|
|
254
285
|
// Valid API key
|
|
255
286
|
const res = await app.request("/test", {
|
|
256
|
-
headers: { "x-api-key": "valid-key" }
|
|
287
|
+
headers: { "x-api-key": "valid-key" }
|
|
257
288
|
});
|
|
258
289
|
const body = await res.json() as any;
|
|
259
290
|
expect(body.user.userId).toBe("api-user");
|
|
260
291
|
|
|
261
|
-
// Invalid API key
|
|
292
|
+
// Invalid API key — requireAuth not set, defaults to true,
|
|
293
|
+
// so unauthenticated requests are rejected
|
|
262
294
|
const res2 = await app.request("/test", {
|
|
263
|
-
headers: { "x-api-key": "bad-key" }
|
|
295
|
+
headers: { "x-api-key": "bad-key" }
|
|
264
296
|
});
|
|
265
|
-
expect(res2.status).toBe(
|
|
297
|
+
expect(res2.status).toBe(401); // Secure by default
|
|
266
298
|
});
|
|
267
299
|
|
|
268
300
|
it("returns 401 when custom validator throws", async () => {
|
|
269
301
|
const app = new Hono<HonoEnv>();
|
|
270
302
|
app.use("/*", createAuthMiddleware({
|
|
271
303
|
driver: mockDriver,
|
|
272
|
-
validator: async () => { throw new Error("auth failed"); }
|
|
304
|
+
validator: async () => { throw new Error("auth failed"); }
|
|
273
305
|
}));
|
|
274
306
|
app.get("/test", (c) => c.json({ ok: true }));
|
|
275
307
|
|
|
@@ -278,10 +310,11 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
278
310
|
});
|
|
279
311
|
|
|
280
312
|
it("calls withAuth on driver when available", async () => {
|
|
281
|
-
const scopedDriver = { ...mockDriver,
|
|
313
|
+
const scopedDriver = { ...mockDriver,
|
|
314
|
+
isScopedDriver: true };
|
|
282
315
|
const driverWithAuth = {
|
|
283
316
|
...mockDriver,
|
|
284
|
-
withAuth: jest.fn().mockResolvedValue(scopedDriver)
|
|
317
|
+
withAuth: jest.fn().mockResolvedValue(scopedDriver)
|
|
285
318
|
};
|
|
286
319
|
|
|
287
320
|
const app = new Hono<HonoEnv>();
|
|
@@ -293,12 +326,13 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
293
326
|
|
|
294
327
|
const token = generateAccessToken("rls-user", ["editor"]);
|
|
295
328
|
const res = await app.request("/test", {
|
|
296
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
329
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
297
330
|
});
|
|
298
331
|
const body = await res.json() as any;
|
|
299
332
|
expect(body.scoped).toBe(true);
|
|
300
333
|
expect(driverWithAuth.withAuth).toHaveBeenCalledWith(
|
|
301
|
-
expect.objectContaining({ uid: "rls-user",
|
|
334
|
+
expect.objectContaining({ uid: "rls-user",
|
|
335
|
+
roles: ["editor"] })
|
|
302
336
|
);
|
|
303
337
|
});
|
|
304
338
|
|
|
@@ -306,7 +340,8 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
306
340
|
const app = new Hono<HonoEnv>();
|
|
307
341
|
app.use("/*", createAuthMiddleware({
|
|
308
342
|
driver: mockDriver,
|
|
309
|
-
|
|
343
|
+
requireAuth: false, // Explicit opt-out for this test
|
|
344
|
+
validator: async () => true
|
|
310
345
|
}));
|
|
311
346
|
app.get("/test", (c) => {
|
|
312
347
|
const user = c.get("user");
|
|
@@ -318,4 +353,204 @@ describe("Auth Middleware (Hono)", () => {
|
|
|
318
353
|
expect(body.user.userId).toBe("default");
|
|
319
354
|
});
|
|
320
355
|
});
|
|
356
|
+
|
|
357
|
+
describe("Service Key Authentication", () => {
|
|
358
|
+
const SERVICE_KEY = "a-very-secure-service-key-that-is-at-least-32-characters-long";
|
|
359
|
+
const mockDriver: DataDriver = {
|
|
360
|
+
fetchCollection: jest.fn() as any,
|
|
361
|
+
fetchEntity: jest.fn() as any,
|
|
362
|
+
saveEntity: jest.fn() as any,
|
|
363
|
+
deleteEntity: jest.fn() as any
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
it("grants admin access when Bearer token matches the service key", async () => {
|
|
367
|
+
const app = new Hono<HonoEnv>();
|
|
368
|
+
app.use("/*", createAuthMiddleware({
|
|
369
|
+
driver: mockDriver,
|
|
370
|
+
requireAuth: true,
|
|
371
|
+
serviceKey: SERVICE_KEY
|
|
372
|
+
}));
|
|
373
|
+
app.get("/test", (c) => {
|
|
374
|
+
const user = c.get("user");
|
|
375
|
+
return c.json({ user });
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const res = await app.request("/test", {
|
|
379
|
+
headers: { Authorization: `Bearer ${SERVICE_KEY}` }
|
|
380
|
+
});
|
|
381
|
+
expect(res.status).toBe(200);
|
|
382
|
+
const body = await res.json() as any;
|
|
383
|
+
expect(body.user.userId).toBe("service");
|
|
384
|
+
expect(body.user.roles).toEqual(["admin"]);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("rejects when Bearer token does not match the service key", async () => {
|
|
388
|
+
const app = new Hono<HonoEnv>();
|
|
389
|
+
app.use("/*", createAuthMiddleware({
|
|
390
|
+
driver: mockDriver,
|
|
391
|
+
requireAuth: true,
|
|
392
|
+
serviceKey: SERVICE_KEY
|
|
393
|
+
}));
|
|
394
|
+
app.get("/test", (c) => c.json({ ok: true }));
|
|
395
|
+
|
|
396
|
+
const res = await app.request("/test", {
|
|
397
|
+
headers: { Authorization: "Bearer wrong-key-that-is-definitely-not-correct" }
|
|
398
|
+
});
|
|
399
|
+
// Wrong key is not a valid service key, and also not a valid JWT → 401
|
|
400
|
+
expect(res.status).toBe(401);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("still accepts valid JWT tokens when service key is configured", async () => {
|
|
404
|
+
const token = generateAccessToken("jwt-user", ["editor"]);
|
|
405
|
+
const app = new Hono<HonoEnv>();
|
|
406
|
+
app.use("/*", createAuthMiddleware({
|
|
407
|
+
driver: mockDriver,
|
|
408
|
+
requireAuth: true,
|
|
409
|
+
serviceKey: SERVICE_KEY
|
|
410
|
+
}));
|
|
411
|
+
app.get("/test", (c) => {
|
|
412
|
+
const user = c.get("user");
|
|
413
|
+
return c.json({ user });
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const res = await app.request("/test", {
|
|
417
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
418
|
+
});
|
|
419
|
+
expect(res.status).toBe(200);
|
|
420
|
+
const body = await res.json() as any;
|
|
421
|
+
expect(body.user.userId).toBe("jwt-user");
|
|
422
|
+
expect(body.user.roles).toEqual(["editor"]);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it("scopes driver via withAuth with service identity", async () => {
|
|
426
|
+
const scopedDriver = { scoped: true } as unknown as DataDriver;
|
|
427
|
+
const driverWithAuth = {
|
|
428
|
+
withAuth: jest.fn().mockResolvedValue(scopedDriver)
|
|
429
|
+
} as unknown as DataDriver;
|
|
430
|
+
|
|
431
|
+
const app = new Hono<HonoEnv>();
|
|
432
|
+
app.use("/*", createAuthMiddleware({
|
|
433
|
+
driver: driverWithAuth,
|
|
434
|
+
requireAuth: true,
|
|
435
|
+
serviceKey: SERVICE_KEY
|
|
436
|
+
}));
|
|
437
|
+
app.get("/test", (c) => {
|
|
438
|
+
const d = c.get("driver") as any;
|
|
439
|
+
return c.json({ scoped: d.scoped });
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const res = await app.request("/test", {
|
|
443
|
+
headers: { Authorization: `Bearer ${SERVICE_KEY}` }
|
|
444
|
+
});
|
|
445
|
+
const body = await res.json() as any;
|
|
446
|
+
expect(body.scoped).toBe(true);
|
|
447
|
+
expect((driverWithAuth as any).withAuth).toHaveBeenCalledWith(
|
|
448
|
+
expect.objectContaining({ uid: "service",
|
|
449
|
+
roles: ["admin"] })
|
|
450
|
+
);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("does not activate service key path when no serviceKey is configured", async () => {
|
|
454
|
+
const app = new Hono<HonoEnv>();
|
|
455
|
+
app.use("/*", createAuthMiddleware({
|
|
456
|
+
driver: mockDriver,
|
|
457
|
+
requireAuth: true
|
|
458
|
+
// No serviceKey configured
|
|
459
|
+
}));
|
|
460
|
+
app.get("/test", (c) => c.json({ ok: true }));
|
|
461
|
+
|
|
462
|
+
// Sending a random string should fail as invalid JWT
|
|
463
|
+
const res = await app.request("/test", {
|
|
464
|
+
headers: { Authorization: `Bearer ${SERVICE_KEY}` }
|
|
465
|
+
});
|
|
466
|
+
expect(res.status).toBe(401);
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// ── createRequireAuth (standalone factory) ──────────────
|
|
471
|
+
describe("createRequireAuth", () => {
|
|
472
|
+
const SERVICE_KEY = "a-very-secure-service-key-that-is-at-least-32-characters-long";
|
|
473
|
+
it("returns the default requireAuth when no serviceKey is provided", async () => {
|
|
474
|
+
const middleware = createRequireAuth();
|
|
475
|
+
// The returned function should be the original requireAuth
|
|
476
|
+
expect(middleware).toBe(requireAuth);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it("returns the default requireAuth when serviceKey is undefined", async () => {
|
|
480
|
+
const middleware = createRequireAuth({ serviceKey: undefined });
|
|
481
|
+
expect(middleware).toBe(requireAuth);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it("authenticates with a valid service key", async () => {
|
|
485
|
+
const app = new Hono<HonoEnv>();
|
|
486
|
+
app.use("/*", createRequireAuth({ serviceKey: SERVICE_KEY }));
|
|
487
|
+
app.get("/admin/users", (c) => {
|
|
488
|
+
const user = c.get("user");
|
|
489
|
+
return c.json({ userId: user?.userId,
|
|
490
|
+
roles: user?.roles });
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const res = await app.request("/admin/users", {
|
|
494
|
+
headers: { Authorization: `Bearer ${SERVICE_KEY}` }
|
|
495
|
+
});
|
|
496
|
+
expect(res.status).toBe(200);
|
|
497
|
+
const body = await res.json() as any;
|
|
498
|
+
expect(body.userId).toBe("service");
|
|
499
|
+
expect(body.roles).toEqual(["admin"]);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it("rejects an incorrect service key and falls back to JWT (which also fails)", async () => {
|
|
503
|
+
const app = new Hono<HonoEnv>();
|
|
504
|
+
app.use("/*", createRequireAuth({ serviceKey: SERVICE_KEY }));
|
|
505
|
+
app.get("/admin/users", (c) => c.json({ ok: true }));
|
|
506
|
+
|
|
507
|
+
const res = await app.request("/admin/users", {
|
|
508
|
+
headers: { Authorization: "Bearer wrong-key" }
|
|
509
|
+
});
|
|
510
|
+
expect(res.status).toBe(401);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it("falls back to valid JWT when service key doesn't match", async () => {
|
|
514
|
+
const token = generateAccessToken("user-123", ["admin"]);
|
|
515
|
+
const app = new Hono<HonoEnv>();
|
|
516
|
+
app.use("/*", createRequireAuth({ serviceKey: SERVICE_KEY }));
|
|
517
|
+
app.get("/admin/users", (c) => {
|
|
518
|
+
const user = c.get("user");
|
|
519
|
+
return c.json({ userId: user?.userId,
|
|
520
|
+
roles: user?.roles });
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
const res = await app.request("/admin/users", {
|
|
524
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
525
|
+
});
|
|
526
|
+
expect(res.status).toBe(200);
|
|
527
|
+
const body = await res.json() as any;
|
|
528
|
+
expect(body.userId).toBe("user-123");
|
|
529
|
+
expect(body.roles).toEqual(["admin"]);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("returns 401 when no authorization header is provided", async () => {
|
|
533
|
+
const app = new Hono<HonoEnv>();
|
|
534
|
+
app.use("/*", createRequireAuth({ serviceKey: SERVICE_KEY }));
|
|
535
|
+
app.get("/admin/users", (c) => c.json({ ok: true }));
|
|
536
|
+
|
|
537
|
+
const res = await app.request("/admin/users");
|
|
538
|
+
expect(res.status).toBe(401);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("works with requireAdmin for service key (admin role is granted)", async () => {
|
|
542
|
+
const app = new Hono<HonoEnv>();
|
|
543
|
+
app.use("/*", createRequireAuth({ serviceKey: SERVICE_KEY }));
|
|
544
|
+
app.get("/admin/users", requireAdmin, (c) => {
|
|
545
|
+
return c.json({ authorized: true });
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
const res = await app.request("/admin/users", {
|
|
549
|
+
headers: { Authorization: `Bearer ${SERVICE_KEY}` }
|
|
550
|
+
});
|
|
551
|
+
expect(res.status).toBe(200);
|
|
552
|
+
const body = await res.json() as any;
|
|
553
|
+
expect(body.authorized).toBe(true);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
321
556
|
});
|