@hexis-ai/engram-server 0.11.3 → 0.12.0

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/server.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Hono } from "hono";
2
+ import { cors } from "hono/cors";
2
3
  import { log, newRequestId } from "./logger";
3
4
  import { createAdminRouter } from "./admin";
4
5
  import { aliasesRoutes } from "./routes/aliases";
@@ -39,6 +40,28 @@ export function createServer(opts) {
39
40
  });
40
41
  }
41
42
  });
43
+ // CORS — required so engram-web (different origin) can carry the
44
+ // auth cookie. Applies to /auth/* and /v1/* only; admin uses a
45
+ // bearer token from server-side callers and doesn't need CORS.
46
+ if (opts.corsOrigins && opts.corsOrigins.length > 0) {
47
+ const origins = opts.corsOrigins;
48
+ const corsMw = cors({
49
+ origin: (o) => (origins.includes(o) ? o : null),
50
+ credentials: true,
51
+ allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
52
+ allowHeaders: [
53
+ "content-type",
54
+ "x-api-key",
55
+ "x-workspace-id",
56
+ "x-request-id",
57
+ "authorization",
58
+ ],
59
+ exposeHeaders: ["x-request-id"],
60
+ maxAge: 600,
61
+ });
62
+ app.use("/auth/*", corsMw);
63
+ app.use("/v1/*", corsMw);
64
+ }
42
65
  app.onError((err, c) => {
43
66
  log.error("unhandled", {
44
67
  request_id: c.var.request_id,
@@ -71,22 +94,60 @@ export function createServer(opts) {
71
94
  if (opts.admin) {
72
95
  app.route("/admin/v1", createAdminRouter(opts.admin));
73
96
  }
74
- // Workspace auth gate every `/v1/*` route runs behind this.
97
+ // better-auth catchall. Mounted before `/v1` so /auth/* never falls
98
+ // through to the workspace gate.
99
+ if (opts.authHandler) {
100
+ const handler = opts.authHandler;
101
+ app.all("/auth/*", (c) => handler.handler(c.req.raw));
102
+ }
103
+ // Workspace auth gate — every `/v1/*` route runs behind this. Tries
104
+ // api-key first; falls back to cookie session when configured. The
105
+ // Bearer header is reserved for api-keys here; engram-web uses the
106
+ // session cookie, not a Bearer.
75
107
  app.use("/v1/*", async (c, next) => {
76
108
  const apiKey = c.req.header("x-api-key") ??
77
109
  c.req.header("authorization")?.match(/^Bearer\s+(.+)$/i)?.[1];
78
- if (!apiKey)
79
- return c.json({ error: "unauthorized" }, 401);
80
- const ctx = await opts.auth(apiKey);
110
+ let ctx = apiKey ? await opts.auth(apiKey) : null;
111
+ if (!ctx && !apiKey && opts.cookieAuth) {
112
+ ctx = await opts.cookieAuth(c.req.raw);
113
+ }
81
114
  if (!ctx)
82
115
  return c.json({ error: "unauthorized" }, 401);
83
116
  c.set("ctx", ctx);
84
117
  await next();
85
118
  });
86
- // Identity probe — echoes the workspace the caller's key resolves to.
87
- // Used by host clients (e.g. monet's `/v1/engram/*` proxy) to label
88
- // which tenant they're viewing.
119
+ // Identity probe — echoes the workspace the caller's auth
120
+ // resolves to. Cheap, used as a health/whoami by clients.
89
121
  app.get("/v1/me", (c) => c.json({ workspaceId: c.var.ctx.workspaceId }));
122
+ // Cookie-auth helpers for engram-web: list every workspace / org
123
+ // the signed-in user can reach. api-key callers don't need these
124
+ // (they already know which workspace they're scoped to).
125
+ if (opts.authHandler && opts.orgStore) {
126
+ const auth = opts.authHandler;
127
+ const orgStore = opts.orgStore;
128
+ const requireUser = async (req) => {
129
+ const s = await auth.api.getSession({ headers: req.headers }).catch(() => null);
130
+ return s?.user ?? null;
131
+ };
132
+ app.get("/v1/me/workspaces", async (c) => {
133
+ const user = await requireUser(c.req.raw);
134
+ if (!user)
135
+ return c.json({ error: "unauthorized" }, 401);
136
+ const workspaces = await orgStore.listWorkspacesForUser(user.id);
137
+ return c.json({ workspaces });
138
+ });
139
+ app.get("/v1/me/orgs", async (c) => {
140
+ const user = await requireUser(c.req.raw);
141
+ if (!user)
142
+ return c.json({ error: "unauthorized" }, 401);
143
+ const memberships = await orgStore.listOrgsForUser(user.id);
144
+ const orgs = await Promise.all(memberships.map(async (m) => ({
145
+ ...(await orgStore.getOrg(m.orgId)),
146
+ role: m.role,
147
+ })));
148
+ return c.json({ orgs: orgs.filter((o) => o.id) });
149
+ });
150
+ }
90
151
  app.route("/v1", sessionsRoutes(cfg));
91
152
  app.route("/v1", personsRoutes(cfg));
92
153
  app.route("/v1", identitiesRoutes(cfg));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexis-ai/engram-server",
3
- "version": "0.11.3",
3
+ "version": "0.12.0",
4
4
  "description": "Engram server: ingest agent session events, persist via a pluggable adapter, expose search.",
5
5
  "keywords": [
6
6
  "engram",
@@ -50,8 +50,10 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@hexis-ai/engram-core": "^0.2.0",
53
- "@hexis-ai/engram-sdk": "^0.12.0",
53
+ "@hexis-ai/engram-sdk": "^0.13.0",
54
+ "better-auth": "^1.6.11",
54
55
  "hono": "^4.6.0",
56
+ "pg": "^8.13.0",
55
57
  "zod": "^4.0.0"
56
58
  },
57
59
  "peerDependencies": {
@@ -63,6 +65,7 @@
63
65
  }
64
66
  },
65
67
  "devDependencies": {
68
+ "@types/pg": "^8.11.10",
66
69
  "postgres": "^3.4.0"
67
70
  },
68
71
  "publishConfig": {