@classytic/arc 2.11.3 → 2.13.1
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/README.md +27 -18
- package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
- package/dist/EventTransport-CT_52aWU.d.mts +34 -0
- package/dist/EventTransport-DLWoUMHy.mjs +103 -0
- package/dist/{QueryCache-DOBNHBE0.d.mts → QueryCache-D41bfdBB.d.mts} +1 -1
- package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +1 -1
- package/dist/auth/audit.d.mts +199 -0
- package/dist/auth/audit.mjs +288 -0
- package/dist/auth/index.d.mts +5 -5
- package/dist/auth/index.mjs +117 -191
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
- package/dist/buildHandler-olo-gt94.mjs +610 -0
- package/dist/cache/index.d.mts +3 -3
- package/dist/cache/index.mjs +3 -3
- package/dist/cli/commands/describe.d.mts +89 -13
- package/dist/cli/commands/describe.mjs +56 -2
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +147 -48
- package/dist/cli/commands/init.d.mts +13 -0
- package/dist/cli/commands/init.mjs +237 -112
- package/dist/cli/commands/introspect.mjs +8 -1
- package/dist/context/index.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -5
- package/dist/core-D72ia0EH.mjs +1399 -0
- package/dist/{createActionRouter-u3ql2EDo.mjs → createActionRouter-CEvzKcy8.mjs} +7 -20
- package/dist/createAggregationRouter-CyecOxnO.mjs +114 -0
- package/dist/{createApp-BFxtdKy6.mjs → createApp-XX2-N0Yd.mjs} +31 -27
- package/dist/defineEvent-D5h7EvAx.mjs +188 -0
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
- package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
- package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
- package/dist/errors-j4aJm1Wg.mjs +184 -0
- package/dist/{eventPlugin-KrFIQ097.mjs → eventPlugin-CaKTYkYM.mjs} +35 -137
- package/dist/{eventPlugin-CUNjYYRY.d.mts → eventPlugin-qXpqTebY.d.mts} +57 -7
- package/dist/events/index.d.mts +164 -5
- package/dist/events/index.mjs +133 -209
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +204 -31
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +2 -2
- package/dist/factory/index.mjs +2 -2
- package/dist/{fields-C8Y0XLAu.d.mts → fields-COhcH3fk.d.mts} +23 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/index.mjs +1 -20
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-BYCqHCVu.d.mts → index-BTqLEvhu.d.mts} +164 -4
- package/dist/{index-6u4_Gg6G.d.mts → index-BtW7qYwa.d.mts} +661 -281
- package/dist/{index-BdXnTPRj.d.mts → index-Ds61mrJE.d.mts} +50 -4
- package/dist/{index-DdQ3O9Pg.d.mts → index-Dz5IKsrE.d.mts} +360 -219
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +9 -10
- package/dist/integrations/event-gateway.d.mts +2 -2
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +60 -11
- package/dist/integrations/streamline.mjs +75 -85
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- package/dist/integrations/websocket.mjs +2 -8
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +2 -2
- package/dist/migrations/index.d.mts +23 -3
- package/dist/migrations/index.mjs +0 -7
- package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
- package/dist/{openapi-BGUn7Ki1.mjs → openapi-CiOMVW1p.mjs} +143 -13
- package/dist/org/index.d.mts +2 -2
- package/dist/org/index.mjs +1 -1
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
- package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +18 -33
- package/dist/plugins/index.mjs +33 -13
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +5 -5
- package/dist/presets/filesUpload.mjs +6 -9
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +2 -2
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +6 -8
- package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
- package/dist/{queryCachePlugin-BUXBSm4F.d.mts → queryCachePlugin-CqMdLI2-.d.mts} +2 -2
- package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
- package/dist/{redis-Cm1gnRDf.d.mts → redis-DiMkdHEl.d.mts} +1 -1
- package/dist/redis-stream-D6HzR1Z_.d.mts +232 -0
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
- package/dist/{resourceToTools-ByZpgjeH.mjs → resourceToTools-C5coh64w.mjs} +224 -71
- package/dist/{routerShared-BqLRb5l7.mjs → routerShared-D6_fEGHh.mjs} +40 -36
- package/dist/{schemaIR-BlG9bY7v.mjs → schemaIR-7Vl611Qs.mjs} +1 -1
- package/dist/schemas/index.d.mts +100 -30
- package/dist/schemas/index.mjs +86 -29
- package/dist/scim/index.d.mts +264 -0
- package/dist/scim/index.mjs +963 -0
- package/dist/scope/index.d.mts +3 -3
- package/dist/scope/index.mjs +4 -4
- package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
- package/dist/{store-helpers-BhrzxvyQ.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
- package/dist/testing/index.d.mts +2 -8
- package/dist/testing/index.mjs +16 -24
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-BH7dEGvU.d.mts → types-BvqwCCSx.d.mts} +77 -29
- package/dist/{types-tgR4Pt8F.d.mts → types-CTYvcwHe.d.mts} +195 -1
- package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
- package/dist/{types-9beEMe25.d.mts → types-DQHFc8PM.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +5 -5
- package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
- package/dist/{versioning-M9lNLhO8.d.mts → versioning-DTTvc80y.d.mts} +1 -1
- package/package.json +24 -34
- package/skills/arc/SKILL.md +521 -785
- package/skills/arc/references/agent-auth.md +238 -0
- package/skills/arc/references/api-reference.md +187 -0
- package/skills/arc/references/auth.md +354 -7
- package/skills/arc/references/enterprise-auth.md +94 -0
- package/skills/arc/references/events.md +8 -6
- package/skills/arc/references/mcp.md +2 -2
- package/skills/arc/references/multi-tenancy.md +11 -2
- package/skills/arc/references/production.md +10 -9
- package/skills/arc/references/scim.md +247 -0
- package/skills/arc/references/testing.md +1 -1
- package/skills/arc-code-review/SKILL.md +141 -0
- package/skills/arc-code-review/references/anti-patterns.md +911 -0
- package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
- package/skills/arc-code-review/references/migration-recipes.md +700 -0
- package/skills/arc-code-review/references/mongokit-migration.md +386 -0
- package/skills/arc-code-review/references/scaffolding.md +230 -0
- package/skills/arc-code-review/references/severity.md +127 -0
- package/dist/EventTransport-CfVEGaEl.d.mts +0 -293
- package/dist/adapters/index.d.mts +0 -3
- package/dist/adapters/index.mjs +0 -2
- package/dist/adapters-D0tT2Tyo.mjs +0 -949
- package/dist/auth/mongoose.d.mts +0 -191
- package/dist/auth/mongoose.mjs +0 -73
- package/dist/core-DnUsRpuX.mjs +0 -1049
- package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
- package/dist/errorHandler-Co3lnVmJ.d.mts +0 -114
- package/dist/errors-D5c-5BJL.mjs +0 -232
- package/dist/index-BbMrcvGp.d.mts +0 -362
- package/dist/redis-stream-CM8TXTix.d.mts +0 -110
- /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
- /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
- /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
- /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
- /package/dist/{elevation-s5ykdNHr.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
- /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BD5nw6St.d.mts} +0 -0
- /package/dist/{interface-CkkWm5uR.d.mts → interface-DfLGcus7.d.mts} +0 -0
- /package/dist/{interface-Da0r7Lna.d.mts → interface-beEtJyWM.d.mts} +0 -0
- /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
- /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
- /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
- /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
- /package/dist/{pluralize-BneOJkpi.mjs → pluralize-DQgqgifU.mjs} +0 -0
- /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
- /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
- /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
- /package/dist/{sessionManager-D-oNWHz3.d.mts → sessionManager-C4Le_UB3.d.mts} +0 -0
- /package/dist/{storage-BwGQXUpd.d.mts → storage-Dfzt4VTl.d.mts} +0 -0
- /package/dist/{tracing-DokiEsuz.d.mts → tracing-QJVprktp.d.mts} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
- /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
- /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
- /package/dist/{websocket-CyJ1VIFI.d.mts → websocket-ChC2rqe1.d.mts} +0 -0
package/dist/auth/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { t as
|
|
4
|
-
import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi
|
|
1
|
+
import { d as createDomainError, l as UnauthorizedError, p as isArcError, t as ArcError } from "../errors-j4aJm1Wg.mjs";
|
|
2
|
+
import { _ as requireOrgMembership, v as requireOrgRole, x as requireTeamMembership } from "../permissions-ohQyv50e.mjs";
|
|
3
|
+
import { n as normalizeRoles, t as getUserRoles } from "../types-D57iXYb8.mjs";
|
|
4
|
+
import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi--M_i87dQ.mjs";
|
|
5
5
|
import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
6
6
|
import fp from "fastify-plugin";
|
|
7
7
|
//#region src/auth/authPlugin.ts
|
|
@@ -88,17 +88,17 @@ const authPlugin = async (fastify, opts = {}) => {
|
|
|
88
88
|
const token = resolveToken(request);
|
|
89
89
|
if (token) {
|
|
90
90
|
const decoded = jwtContext.verify(token);
|
|
91
|
-
if (decoded.type === "refresh") throw
|
|
92
|
-
if (strictTokenType && decoded.type !== "access") throw
|
|
91
|
+
if (decoded.type === "refresh") throw createDomainError("arc.auth.invalid_token_type", "Refresh tokens cannot be used for authentication", 401);
|
|
92
|
+
if (strictTokenType && decoded.type !== "access") throw createDomainError("arc.auth.invalid_token_type", "Invalid token type: expected access token", 401);
|
|
93
93
|
user = decoded;
|
|
94
94
|
}
|
|
95
|
-
} else throw
|
|
96
|
-
if (!user) throw new
|
|
95
|
+
} else throw createDomainError("arc.auth.misconfigured", "No authenticator configured. Provide auth.authenticate function or auth.jwt.secret.", 500);
|
|
96
|
+
if (!user) throw new UnauthorizedError("Authentication required");
|
|
97
97
|
if (isRevoked) try {
|
|
98
|
-
if (await isRevoked(user)) throw
|
|
98
|
+
if (await isRevoked(user)) throw createDomainError("arc.auth.token_revoked", "Token has been revoked", 401);
|
|
99
99
|
} catch (revokeErr) {
|
|
100
|
-
if (revokeErr
|
|
101
|
-
throw
|
|
100
|
+
if (isArcError(revokeErr) && revokeErr.code === "arc.auth.token_revoked") throw revokeErr;
|
|
101
|
+
throw createDomainError("arc.auth.revocation_check_failed", "Token revocation check failed", 401);
|
|
102
102
|
}
|
|
103
103
|
const reqRecord = request;
|
|
104
104
|
reqRecord.user = user;
|
|
@@ -126,11 +126,20 @@ const authPlugin = async (fastify, opts = {}) => {
|
|
|
126
126
|
await onFailure(request, reply, error);
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
|
+
if (isArcError(error)) {
|
|
130
|
+
const message = exposeAuthErrors ? error.message : "Authentication required";
|
|
131
|
+
reply.code(error.statusCode).send({
|
|
132
|
+
code: error.code,
|
|
133
|
+
message,
|
|
134
|
+
status: error.statusCode
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
129
138
|
const message = exposeAuthErrors ? error.message : "Authentication required";
|
|
130
139
|
reply.code(401).send({
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
code: "arc.unauthorized",
|
|
141
|
+
message,
|
|
142
|
+
status: 401
|
|
134
143
|
});
|
|
135
144
|
}
|
|
136
145
|
};
|
|
@@ -193,7 +202,7 @@ const authPlugin = async (fastify, opts = {}) => {
|
|
|
193
202
|
* App calls this after validating credentials (login, OAuth, etc.)
|
|
194
203
|
*/
|
|
195
204
|
const issueTokens = (payload, options) => {
|
|
196
|
-
if (!jwtContext) throw
|
|
205
|
+
if (!jwtContext) throw createDomainError("arc.auth.misconfigured", "JWT not configured. Provide auth.jwt.secret to use issueTokens.", 500);
|
|
197
206
|
const accessTtl = options?.expiresIn ?? accessExpiresIn;
|
|
198
207
|
const refreshTtl = options?.refreshExpiresIn ?? refreshExpiresIn;
|
|
199
208
|
const accessToken = jwtContext.sign({
|
|
@@ -228,9 +237,9 @@ const authPlugin = async (fastify, opts = {}) => {
|
|
|
228
237
|
* App calls this in refresh endpoint
|
|
229
238
|
*/
|
|
230
239
|
const verifyRefreshToken = (token) => {
|
|
231
|
-
if (!jwtContext) throw
|
|
240
|
+
if (!jwtContext) throw createDomainError("arc.auth.misconfigured", "JWT not configured. Provide auth.jwt.secret to use verifyRefreshToken.", 500);
|
|
232
241
|
const decoded = fastify.jwt.verify(token, { ...refreshSecret !== jwtConfig?.secret ? { key: refreshSecret } : {} });
|
|
233
|
-
if (decoded.type !== "refresh") throw
|
|
242
|
+
if (decoded.type !== "refresh") throw createDomainError("arc.auth.invalid_token_type", "Invalid token type: expected refresh token", 401);
|
|
234
243
|
return decoded;
|
|
235
244
|
};
|
|
236
245
|
/**
|
|
@@ -246,9 +255,9 @@ const authPlugin = async (fastify, opts = {}) => {
|
|
|
246
255
|
const user = reqRecord[userProperty] ?? reqRecord.user;
|
|
247
256
|
if (!user) {
|
|
248
257
|
reply.code(401).send({
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
258
|
+
code: "arc.unauthorized",
|
|
259
|
+
message: "No user context",
|
|
260
|
+
status: 401
|
|
252
261
|
});
|
|
253
262
|
return;
|
|
254
263
|
}
|
|
@@ -256,9 +265,9 @@ const authPlugin = async (fastify, opts = {}) => {
|
|
|
256
265
|
if (allowedRoles.length === 1 && allowedRoles[0] === "*") return;
|
|
257
266
|
if (!allowedRoles.some((role) => userRoles.includes(role))) {
|
|
258
267
|
reply.code(403).send({
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
268
|
+
code: "arc.forbidden",
|
|
269
|
+
message: `Requires one of: ${allowedRoles.join(", ")}`,
|
|
270
|
+
status: 403
|
|
262
271
|
});
|
|
263
272
|
return;
|
|
264
273
|
}
|
|
@@ -334,65 +343,84 @@ async function sendFetchResponse(response, reply) {
|
|
|
334
343
|
}
|
|
335
344
|
}
|
|
336
345
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
346
|
+
* Resolve the current session via Better Auth's direct JS API.
|
|
347
|
+
*
|
|
348
|
+
* Throws `ArcError(BETTER_AUTH_API_MISSING)` when `auth.api.getSession` is
|
|
349
|
+
* absent — this surfaces immediately and clearly when an integrator passes a
|
|
350
|
+
* stub handler instead of a real `betterAuth()` instance.
|
|
339
351
|
*/
|
|
340
|
-
async function
|
|
352
|
+
async function getSessionDirect(auth, headers) {
|
|
341
353
|
const api = auth.api;
|
|
342
|
-
if (!api || typeof api.getSession !== "function")
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return null;
|
|
349
|
-
}
|
|
354
|
+
if (!api || typeof api.getSession !== "function") throw new ArcError("Better Auth instance is missing `api.getSession` — arc 2.13+ requires the in-process API map. Pass a real `betterAuth()` instance or supply an `api: { getSession }` stub.", {
|
|
355
|
+
code: "BETTER_AUTH_API_MISSING",
|
|
356
|
+
statusCode: 500
|
|
357
|
+
});
|
|
358
|
+
const result = await api.getSession({ headers });
|
|
359
|
+
return result?.user ? result : null;
|
|
350
360
|
}
|
|
351
361
|
/**
|
|
352
|
-
*
|
|
353
|
-
*
|
|
362
|
+
* Read a method from `auth.api` — supports both the flat shape that real
|
|
363
|
+
* `betterAuth()` instances expose (`api.getActiveMember`) and the nested
|
|
364
|
+
* shape some test mocks / older builds use (`api.organization.getActiveMember`).
|
|
365
|
+
*
|
|
366
|
+
* Real Better Auth 1.6.x flattens every plugin endpoint onto the top-level
|
|
367
|
+
* `api` object. The nested form is kept as a fallback for hand-rolled stubs.
|
|
354
368
|
*/
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
if (
|
|
369
|
+
function pickApiMethod(auth, name, group) {
|
|
370
|
+
const api = auth.api;
|
|
371
|
+
if (!api) return void 0;
|
|
372
|
+
const flat = api[name];
|
|
373
|
+
if (typeof flat === "function") return flat;
|
|
374
|
+
if (group) {
|
|
375
|
+
const nested = api[group]?.[name];
|
|
376
|
+
if (typeof nested === "function") return nested;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/** Resolve org roles for the active member (session.activeOrganizationId path). */
|
|
380
|
+
async function getActiveMemberRoles(auth, headers) {
|
|
381
|
+
const fn = pickApiMethod(auth, "getActiveMember", "organization");
|
|
382
|
+
if (!fn) return null;
|
|
358
383
|
try {
|
|
359
|
-
const memberData = await
|
|
360
|
-
|
|
361
|
-
return null;
|
|
384
|
+
const memberData = await fn({ headers });
|
|
385
|
+
return memberData ? extractRolesFromMembership(memberData) : null;
|
|
362
386
|
} catch {
|
|
363
387
|
return null;
|
|
364
388
|
}
|
|
365
389
|
}
|
|
366
390
|
/**
|
|
367
|
-
*
|
|
391
|
+
* Resolve org roles for an explicit organizationId.
|
|
368
392
|
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* active organization set — callers pass org context via `x-organization-id` header.
|
|
393
|
+
* Required for API key auth where the synthetic session lacks
|
|
394
|
+
* `activeOrganizationId` — callers pass org context via the
|
|
395
|
+
* `x-organization-id` header.
|
|
373
396
|
*/
|
|
374
|
-
async function
|
|
375
|
-
const
|
|
376
|
-
if (
|
|
397
|
+
async function getMemberRolesByOrg(auth, headers, organizationId) {
|
|
398
|
+
const fn = pickApiMethod(auth, "getActiveMemberRole", "organization");
|
|
399
|
+
if (!fn) return null;
|
|
377
400
|
try {
|
|
378
|
-
const result = await
|
|
401
|
+
const result = await fn({
|
|
379
402
|
headers,
|
|
380
403
|
query: { organizationId }
|
|
381
404
|
});
|
|
382
|
-
|
|
383
|
-
return null;
|
|
405
|
+
return result?.role ? normalizeRoles(result.role) : null;
|
|
384
406
|
} catch {
|
|
385
407
|
return null;
|
|
386
408
|
}
|
|
387
409
|
}
|
|
388
410
|
/**
|
|
389
|
-
*
|
|
411
|
+
* List teams the current user is a member of. Used to validate
|
|
412
|
+
* `activeTeamId` against the membership set before binding it to scope.
|
|
413
|
+
*
|
|
414
|
+
* Better Auth 1.6+ exposes this as `auth.api.listUserTeams` (path:
|
|
415
|
+
* `/organization/list-user-teams`). Older 1.5.x exposed
|
|
416
|
+
* `auth.api.listTeams` — kept as a fallback so stubs/older versions still
|
|
417
|
+
* work.
|
|
390
418
|
*/
|
|
391
|
-
async function
|
|
392
|
-
const
|
|
393
|
-
if (
|
|
419
|
+
async function listTeamsDirect(auth, headers) {
|
|
420
|
+
const fn = pickApiMethod(auth, "listUserTeams", "organization") ?? pickApiMethod(auth, "listTeams", "organization");
|
|
421
|
+
if (!fn) return null;
|
|
394
422
|
try {
|
|
395
|
-
const result = await
|
|
423
|
+
const result = await fn({ headers });
|
|
396
424
|
const teams = Array.isArray(result) ? result : result?.teams;
|
|
397
425
|
return Array.isArray(teams) ? teams : null;
|
|
398
426
|
} catch {
|
|
@@ -432,61 +460,6 @@ function extractRolesFromMembership(membership) {
|
|
|
432
460
|
}
|
|
433
461
|
return [];
|
|
434
462
|
}
|
|
435
|
-
/** Match an organization membership entry against the active org id */
|
|
436
|
-
function membershipMatchesOrg(membership, activeOrgId) {
|
|
437
|
-
return [
|
|
438
|
-
normalizeId(membership.organizationId),
|
|
439
|
-
normalizeId(membership.orgId),
|
|
440
|
-
normalizeId(membership.id),
|
|
441
|
-
normalizeId(membership.organization?._id),
|
|
442
|
-
normalizeId(membership.organization?.id),
|
|
443
|
-
normalizeId(membership.organization?.organizationId)
|
|
444
|
-
].filter(Boolean).includes(activeOrgId);
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Resolve org roles with fallback chain:
|
|
448
|
-
* 1) GET /organization/get-active-member (requires activeOrganizationId in session)
|
|
449
|
-
* 2) GET /organization/get-active-member-role?organizationId=... (explicit org — works for API key auth)
|
|
450
|
-
* 3) GET /organization/list (fallback for type mismatch/legacy ID storage)
|
|
451
|
-
*/
|
|
452
|
-
async function resolveOrgRoles(auth, protocol, host, normalizedBase, headers, activeOrgId) {
|
|
453
|
-
const memberUrl = `${protocol}://${host}${normalizedBase}/organization/get-active-member`;
|
|
454
|
-
const memberRequest = new Request(memberUrl, {
|
|
455
|
-
method: "GET",
|
|
456
|
-
headers
|
|
457
|
-
});
|
|
458
|
-
const memberResponse = await auth.handler(memberRequest);
|
|
459
|
-
if (memberResponse.ok) {
|
|
460
|
-
const memberData = await memberResponse.json();
|
|
461
|
-
if (memberData) return extractRolesFromMembership(memberData);
|
|
462
|
-
}
|
|
463
|
-
const roleUrl = `${protocol}://${host}${normalizedBase}/organization/get-active-member-role?organizationId=${encodeURIComponent(activeOrgId)}`;
|
|
464
|
-
const roleRequest = new Request(roleUrl, {
|
|
465
|
-
method: "GET",
|
|
466
|
-
headers
|
|
467
|
-
});
|
|
468
|
-
const roleResponse = await auth.handler(roleRequest);
|
|
469
|
-
if (roleResponse.ok) {
|
|
470
|
-
const roleData = await roleResponse.json();
|
|
471
|
-
if (roleData?.role) return normalizeRoles(roleData.role);
|
|
472
|
-
}
|
|
473
|
-
const listUrl = `${protocol}://${host}${normalizedBase}/organization/list`;
|
|
474
|
-
const listRequest = new Request(listUrl, {
|
|
475
|
-
method: "GET",
|
|
476
|
-
headers
|
|
477
|
-
});
|
|
478
|
-
const listResponse = await auth.handler(listRequest);
|
|
479
|
-
if (!listResponse.ok) return null;
|
|
480
|
-
const listData = await listResponse.json();
|
|
481
|
-
const memberships = Array.isArray(listData) ? listData : listData?.organizations ?? listData?.data ?? [];
|
|
482
|
-
if (!Array.isArray(memberships)) return null;
|
|
483
|
-
const target = memberships.find((entry) => {
|
|
484
|
-
if (!entry || typeof entry !== "object") return false;
|
|
485
|
-
return membershipMatchesOrg(entry, activeOrgId);
|
|
486
|
-
});
|
|
487
|
-
if (!target) return null;
|
|
488
|
-
return extractRolesFromMembership(target);
|
|
489
|
-
}
|
|
490
463
|
/**
|
|
491
464
|
* Create a Better Auth adapter for Arc/Fastify.
|
|
492
465
|
*
|
|
@@ -527,33 +500,13 @@ function createBetterAuthAdapter(options) {
|
|
|
527
500
|
*/
|
|
528
501
|
const authenticate = async (request, reply) => {
|
|
529
502
|
try {
|
|
530
|
-
const protocol = request.protocol ?? "http";
|
|
531
|
-
const host = request.hostname ?? "localhost";
|
|
532
503
|
const headers = buildHeaders(request);
|
|
533
|
-
|
|
534
|
-
sessionData = await tryDirectGetSession(auth, headers);
|
|
535
|
-
if (!sessionData) {
|
|
536
|
-
const sessionUrl = `${protocol}://${host}${normalizedBase}/get-session`;
|
|
537
|
-
const sessionRequest = new Request(sessionUrl, {
|
|
538
|
-
method: "GET",
|
|
539
|
-
headers
|
|
540
|
-
});
|
|
541
|
-
const sessionResponse = await auth.handler(sessionRequest);
|
|
542
|
-
if (!sessionResponse.ok) {
|
|
543
|
-
reply.code(401).send({
|
|
544
|
-
success: false,
|
|
545
|
-
error: "Unauthorized",
|
|
546
|
-
message: "Invalid or expired session"
|
|
547
|
-
});
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
sessionData = await sessionResponse.json();
|
|
551
|
-
}
|
|
504
|
+
const sessionData = await getSessionDirect(auth, headers);
|
|
552
505
|
if (!sessionData?.user) {
|
|
553
506
|
reply.code(401).send({
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
507
|
+
code: "arc.unauthorized",
|
|
508
|
+
message: "Invalid or expired session",
|
|
509
|
+
status: 401
|
|
557
510
|
});
|
|
558
511
|
return;
|
|
559
512
|
}
|
|
@@ -572,9 +525,8 @@ function createBetterAuthAdapter(options) {
|
|
|
572
525
|
const session = sessionData.session;
|
|
573
526
|
const activeOrgId = session?.activeOrganizationId || request.headers["x-organization-id"];
|
|
574
527
|
if (activeOrgId) {
|
|
575
|
-
let orgRoles = await
|
|
576
|
-
if (!orgRoles) orgRoles = await
|
|
577
|
-
if (!orgRoles) orgRoles = await resolveOrgRoles(auth, protocol, host, normalizedBase, headers, activeOrgId);
|
|
528
|
+
let orgRoles = await getActiveMemberRoles(auth, headers);
|
|
529
|
+
if (!orgRoles) orgRoles = await getMemberRolesByOrg(auth, headers, activeOrgId);
|
|
578
530
|
if (orgRoles) {
|
|
579
531
|
const scope = {
|
|
580
532
|
kind: "member",
|
|
@@ -585,21 +537,7 @@ function createBetterAuthAdapter(options) {
|
|
|
585
537
|
};
|
|
586
538
|
const activeTeamId = session?.activeTeamId;
|
|
587
539
|
if (activeTeamId) {
|
|
588
|
-
|
|
589
|
-
if (!teams) {
|
|
590
|
-
const teamsUrl = `${protocol}://${host}${normalizedBase}/organization/list-teams`;
|
|
591
|
-
const teamsRequest = new Request(teamsUrl, {
|
|
592
|
-
method: "GET",
|
|
593
|
-
headers
|
|
594
|
-
});
|
|
595
|
-
const teamsResponse = await auth.handler(teamsRequest);
|
|
596
|
-
if (teamsResponse.ok) {
|
|
597
|
-
const teamsData = await teamsResponse.json();
|
|
598
|
-
const teamsList = Array.isArray(teamsData) ? teamsData : teamsData?.teams;
|
|
599
|
-
teams = Array.isArray(teamsList) ? teamsList : [];
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
if (teams?.some((t) => normalizeId(t.id) === activeTeamId)) scope.teamId = activeTeamId;
|
|
540
|
+
if ((await listTeamsDirect(auth, headers))?.some((t) => normalizeId(t.id) === activeTeamId)) scope.teamId = activeTeamId;
|
|
603
541
|
}
|
|
604
542
|
req.scope = scope;
|
|
605
543
|
}
|
|
@@ -608,9 +546,9 @@ function createBetterAuthAdapter(options) {
|
|
|
608
546
|
} catch (err) {
|
|
609
547
|
const message = exposeAuthErrors ? err instanceof Error ? err.message : String(err) : "Authentication required";
|
|
610
548
|
reply.code(401).send({
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
549
|
+
code: "arc.unauthorized",
|
|
550
|
+
message,
|
|
551
|
+
status: 401
|
|
614
552
|
});
|
|
615
553
|
}
|
|
616
554
|
};
|
|
@@ -625,17 +563,7 @@ function createBetterAuthAdapter(options) {
|
|
|
625
563
|
const optionalAuthenticate = async (request, _reply) => {
|
|
626
564
|
try {
|
|
627
565
|
const headers = buildHeaders(request);
|
|
628
|
-
|
|
629
|
-
sessionData = await tryDirectGetSession(auth, headers);
|
|
630
|
-
if (!sessionData) {
|
|
631
|
-
const sessionUrl = `${request.protocol ?? "http"}://${request.hostname ?? "localhost"}${normalizedBase}/get-session`;
|
|
632
|
-
const sessionRequest = new Request(sessionUrl, {
|
|
633
|
-
method: "GET",
|
|
634
|
-
headers
|
|
635
|
-
});
|
|
636
|
-
const sessionResponse = await auth.handler(sessionRequest);
|
|
637
|
-
if (sessionResponse.ok) sessionData = await sessionResponse.json();
|
|
638
|
-
}
|
|
566
|
+
const sessionData = await getSessionDirect(auth, headers);
|
|
639
567
|
if (!sessionData?.user) return;
|
|
640
568
|
const req = request;
|
|
641
569
|
req.user = sessionData.user;
|
|
@@ -651,9 +579,8 @@ function createBetterAuthAdapter(options) {
|
|
|
651
579
|
if (orgEnabled) {
|
|
652
580
|
const activeOrgId = sessionData.session?.activeOrganizationId || request.headers["x-organization-id"];
|
|
653
581
|
if (activeOrgId) {
|
|
654
|
-
let orgRoles = await
|
|
655
|
-
if (!orgRoles) orgRoles = await
|
|
656
|
-
if (!orgRoles) orgRoles = await resolveOrgRoles(auth, request.protocol ?? "http", request.hostname ?? "localhost", normalizedBase, headers, activeOrgId);
|
|
582
|
+
let orgRoles = await getActiveMemberRoles(auth, headers);
|
|
583
|
+
if (!orgRoles) orgRoles = await getMemberRolesByOrg(auth, headers, activeOrgId);
|
|
657
584
|
if (orgRoles) req.scope = {
|
|
658
585
|
kind: "member",
|
|
659
586
|
userId: optUserId,
|
|
@@ -684,7 +611,7 @@ function createBetterAuthAdapter(options) {
|
|
|
684
611
|
if (!fastify.hasDecorator("authenticate")) fastify.decorate("authenticate", authenticate);
|
|
685
612
|
if (!fastify.hasDecorator("optionalAuthenticate")) fastify.decorate("optionalAuthenticate", optionalAuthenticate);
|
|
686
613
|
if (!extractedOpenApi && openapiOpt !== false && auth.api && typeof auth.api === "object") {
|
|
687
|
-
const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi
|
|
614
|
+
const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi--M_i87dQ.mjs").then((n) => n.t);
|
|
688
615
|
extractedOpenApi = extractBetterAuthOpenApi(auth.api, {
|
|
689
616
|
basePath,
|
|
690
617
|
userFields
|
|
@@ -1006,18 +933,17 @@ function createSessionManager(options) {
|
|
|
1006
933
|
const session = request.session;
|
|
1007
934
|
if (!session) {
|
|
1008
935
|
reply.code(401).send({
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
936
|
+
code: "arc.unauthorized",
|
|
937
|
+
message: "Authentication required",
|
|
938
|
+
status: 401
|
|
1012
939
|
});
|
|
1013
940
|
return;
|
|
1014
941
|
}
|
|
1015
942
|
if (Date.now() - session.updatedAt > freshAgeMs) {
|
|
1016
943
|
reply.code(403).send({
|
|
1017
|
-
|
|
1018
|
-
error: "SessionNotFresh",
|
|
944
|
+
code: "arc.forbidden",
|
|
1019
945
|
message: "Session is not fresh. Please re-authenticate to perform this action.",
|
|
1020
|
-
|
|
946
|
+
status: 403
|
|
1021
947
|
});
|
|
1022
948
|
return;
|
|
1023
949
|
}
|
|
@@ -1028,9 +954,9 @@ function createSessionManager(options) {
|
|
|
1028
954
|
const signedValue = parseCookies(typeof cookieHeader === "string" ? cookieHeader : void 0).get(cookieName);
|
|
1029
955
|
if (!signedValue) {
|
|
1030
956
|
reply.code(401).send({
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
957
|
+
code: "arc.unauthorized",
|
|
958
|
+
message: "No session cookie",
|
|
959
|
+
status: 401
|
|
1034
960
|
});
|
|
1035
961
|
return;
|
|
1036
962
|
}
|
|
@@ -1038,9 +964,9 @@ function createSessionManager(options) {
|
|
|
1038
964
|
if (!sessionId) {
|
|
1039
965
|
reply.header("Set-Cookie", buildClearCookieHeader(cookieName, cookieOptions));
|
|
1040
966
|
reply.code(401).send({
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
967
|
+
code: "arc.unauthorized",
|
|
968
|
+
message: "Invalid session",
|
|
969
|
+
status: 401
|
|
1044
970
|
});
|
|
1045
971
|
return;
|
|
1046
972
|
}
|
|
@@ -1048,9 +974,9 @@ function createSessionManager(options) {
|
|
|
1048
974
|
if (!session) {
|
|
1049
975
|
reply.header("Set-Cookie", buildClearCookieHeader(cookieName, cookieOptions));
|
|
1050
976
|
reply.code(401).send({
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
977
|
+
code: "arc.unauthorized",
|
|
978
|
+
message: "Session expired or revoked",
|
|
979
|
+
status: 401
|
|
1054
980
|
});
|
|
1055
981
|
return;
|
|
1056
982
|
}
|
|
@@ -1058,9 +984,9 @@ function createSessionManager(options) {
|
|
|
1058
984
|
await store.delete(sessionId);
|
|
1059
985
|
reply.header("Set-Cookie", buildClearCookieHeader(cookieName, cookieOptions));
|
|
1060
986
|
reply.code(401).send({
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
987
|
+
code: "arc.unauthorized",
|
|
988
|
+
message: "Session expired",
|
|
989
|
+
status: 401
|
|
1064
990
|
});
|
|
1065
991
|
return;
|
|
1066
992
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as SessionData, s as SessionStore } from "../sessionManager-
|
|
1
|
+
import { i as SessionData, s as SessionStore } from "../sessionManager-C4Le_UB3.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/auth/redis-session.d.ts
|
|
4
4
|
/** Minimal Redis client interface — compatible with ioredis */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import { a as toJsonSchema } from "./schemaConverter-
|
|
2
|
+
import { a as toJsonSchema } from "./schemaConverter-De34B1ZG.mjs";
|
|
3
3
|
//#region src/auth/betterAuthOpenApi.ts
|
|
4
4
|
var betterAuthOpenApi_exports = /* @__PURE__ */ __exportAll({ extractBetterAuthOpenApi: () => extractBetterAuthOpenApi });
|
|
5
5
|
/**
|