@dyrected/core 2.5.40 → 2.5.41

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.cjs CHANGED
@@ -395,13 +395,15 @@ async function executeFieldBeforeChange(fields, data, originalDoc, user, db) {
395
395
  let updatedValue = value;
396
396
  if (field.hooks?.beforeChange) {
397
397
  for (const hook of field.hooks.beforeChange) {
398
- updatedValue = await hook({
398
+ const typedHook = hook;
399
+ const hookArgs = {
399
400
  value: updatedValue,
400
401
  originalDoc: originalDoc ?? void 0,
401
402
  data: result,
402
403
  user,
403
404
  db
404
- });
405
+ };
406
+ updatedValue = await typedHook(hookArgs);
405
407
  }
406
408
  result[field.name] = updatedValue;
407
409
  }
@@ -461,12 +463,14 @@ async function executeFieldAfterRead(fields, doc, user, db) {
461
463
  let updatedValue = value;
462
464
  if (field.hooks?.afterRead) {
463
465
  for (const hook of field.hooks.afterRead) {
464
- updatedValue = await hook({
466
+ const typedHook = hook;
467
+ const hookArgs = {
465
468
  value: updatedValue,
466
469
  doc: result,
467
470
  user,
468
471
  db
469
- });
472
+ };
473
+ updatedValue = await typedHook(hookArgs);
470
474
  }
471
475
  result[field.name] = updatedValue;
472
476
  }
@@ -2090,13 +2094,396 @@ var AuthController = class {
2090
2094
  }
2091
2095
  };
2092
2096
 
2093
- // src/controllers/preview.controller.ts
2094
- var import_jose2 = require("jose");
2097
+ // src/controllers/admin-auth.controller.ts
2098
+ var import_node_crypto2 = require("crypto");
2095
2099
  var import_node_util3 = require("util");
2100
+ var import_jose2 = require("jose");
2101
+
2102
+ // src/utils/admin-auth.ts
2103
+ function getAdminAuthCollection(config) {
2104
+ const requestedSlug = config.adminAuth?.collectionSlug;
2105
+ if (requestedSlug) {
2106
+ return config.collections.find((collection) => collection.slug === requestedSlug) ?? null;
2107
+ }
2108
+ return config.collections.find((collection) => collection.slug === "__admins") ?? config.collections.find((collection) => collection.auth) ?? null;
2109
+ }
2110
+ function getPublicAdminAuthConfig(adminAuth) {
2111
+ const providers = (adminAuth?.providers ?? []).map((provider) => ({
2112
+ id: provider.id,
2113
+ type: provider.type,
2114
+ displayName: provider.displayName || humanizeProviderName(provider.id, provider.type),
2115
+ autoRedirect: provider.autoRedirect
2116
+ }));
2117
+ return {
2118
+ mode: adminAuth?.mode ?? "local",
2119
+ collectionSlug: adminAuth?.collectionSlug,
2120
+ provisioningMode: adminAuth?.provisioningMode,
2121
+ providers
2122
+ };
2123
+ }
2124
+ function humanizeProviderName(id, type) {
2125
+ const cleaned = id.replace(/[-_]+/g, " ").trim();
2126
+ if (!cleaned) return type.toUpperCase();
2127
+ return cleaned.replace(/\b\w/g, (char) => char.toUpperCase());
2128
+ }
2129
+
2130
+ // src/controllers/admin-auth.controller.ts
2131
+ var AdminAuthController = class {
2132
+ constructor(config) {
2133
+ this.config = config;
2134
+ }
2135
+ config;
2136
+ providers(c) {
2137
+ return c.json(getPublicAdminAuthConfig(this.config.adminAuth));
2138
+ }
2139
+ async start(c) {
2140
+ const provider = this.getProvider(c.req.param("provider"));
2141
+ if (!provider) {
2142
+ return c.json({ error: true, message: "Admin auth provider not found." }, 404);
2143
+ }
2144
+ const siteId = c.req.header("X-Site-Id") || c.get("siteId");
2145
+ const returnTo = this.normalizeReturnTo(c.req.query("returnTo"), c);
2146
+ if (provider.type === "oidc") {
2147
+ const redirectUrl = await this.buildOidcStartUrl(
2148
+ provider,
2149
+ returnTo,
2150
+ siteId,
2151
+ new URL(c.req.url).origin
2152
+ );
2153
+ return c.redirect(redirectUrl, 302);
2154
+ }
2155
+ if (!provider.startUrl) {
2156
+ return c.json({ error: true, message: "This provider does not expose a start URL." }, 400);
2157
+ }
2158
+ const url = new URL(provider.startUrl);
2159
+ url.searchParams.set("returnTo", returnTo);
2160
+ if (siteId) url.searchParams.set("siteId", siteId);
2161
+ url.searchParams.set("provider", provider.id);
2162
+ return c.redirect(url.toString(), 302);
2163
+ }
2164
+ async callback(c) {
2165
+ const provider = this.getProvider(c.req.param("provider"));
2166
+ if (!provider) {
2167
+ return c.json({ error: true, message: "Admin auth provider not found." }, 404);
2168
+ }
2169
+ try {
2170
+ const exchange = await this.completeProviderAuth(provider, c);
2171
+ const redirectUrl = new URL(exchange.returnTo);
2172
+ redirectUrl.searchParams.set("dyrectedExternalToken", exchange.token);
2173
+ redirectUrl.searchParams.set("dyrectedAdminCollection", exchange.collectionSlug);
2174
+ return c.redirect(redirectUrl.toString(), 302);
2175
+ } catch (error) {
2176
+ const message = error instanceof Error ? error.message : "External authentication failed.";
2177
+ const fallback = this.normalizeReturnTo(c.req.query("returnTo"), c);
2178
+ const redirectUrl = new URL(fallback);
2179
+ redirectUrl.searchParams.set("dyrectedExternalError", message);
2180
+ return c.redirect(redirectUrl.toString(), 302);
2181
+ }
2182
+ }
2183
+ async exchange(c) {
2184
+ const provider = this.getProvider(c.req.param("provider"));
2185
+ if (!provider) {
2186
+ return c.json({ error: true, message: "Admin auth provider not found." }, 404);
2187
+ }
2188
+ try {
2189
+ const exchange = await this.completeProviderAuth(provider, c);
2190
+ return c.json({
2191
+ token: exchange.token,
2192
+ collectionSlug: exchange.collectionSlug,
2193
+ providerId: provider.id
2194
+ });
2195
+ } catch (error) {
2196
+ const message = error instanceof Error ? error.message : "External authentication failed.";
2197
+ const status = message === "Access denied for this site." ? 403 : 401;
2198
+ return c.json({ error: true, message }, status);
2199
+ }
2200
+ }
2201
+ async logout(c) {
2202
+ return c.json({ success: true, message: "Logged out. Discard your token." });
2203
+ }
2204
+ getProvider(id) {
2205
+ if (this.config.adminAuth?.mode !== "external") return void 0;
2206
+ return this.config.adminAuth.providers.find((provider) => provider.id === id);
2207
+ }
2208
+ async completeProviderAuth(provider, c) {
2209
+ const resolved = provider.type === "oidc" ? await this.resolveOidcIdentity(provider, c) : await this.resolveCustomIdentity(provider, c);
2210
+ const identity = resolved.identity;
2211
+ const siteId = c.req.header("X-Site-Id") || c.get("siteId");
2212
+ const adminCollection = getAdminAuthCollection(this.config);
2213
+ if (!adminCollection?.auth) {
2214
+ throw new Error("Admin auth collection is not configured.");
2215
+ }
2216
+ const db = c.get("config").db;
2217
+ if (!db) throw new Error("Database not configured.");
2218
+ let user = await this.findUserByExternalIdentity(db, adminCollection.slug, provider.id, identity.sub) ?? (identity.email ? (await db.find({
2219
+ collection: adminCollection.slug,
2220
+ where: { email: identity.email },
2221
+ limit: 1
2222
+ })).docs[0] : null);
2223
+ const provisioningMode = this.config.adminAuth?.provisioningMode ?? "jit_plus_membership_management";
2224
+ const allowJitProvisioning = provisioningMode !== "preprovisioned_only" && provider.allowJitProvisioning !== false;
2225
+ if (!user && !allowJitProvisioning) {
2226
+ throw new Error("This account has not been provisioned for admin access.");
2227
+ }
2228
+ const access = await this.resolveAccess(identity, provider.id, siteId, c, user);
2229
+ if (!access.allowed) {
2230
+ throw new Error("Access denied for this site.");
2231
+ }
2232
+ if (!user && allowJitProvisioning) {
2233
+ user = await db.create({
2234
+ collection: adminCollection.slug,
2235
+ data: {
2236
+ ...await this.buildUserData(identity, provider.id, access.roles, access.data),
2237
+ password: await hashPassword((0, import_node_crypto2.randomBytes)(32).toString("hex"))
2238
+ }
2239
+ });
2240
+ } else if (user) {
2241
+ user = await db.update({
2242
+ collection: adminCollection.slug,
2243
+ id: user.id,
2244
+ data: await this.buildUserData(identity, provider.id, access.roles, access.data)
2245
+ });
2246
+ }
2247
+ if (!user?.email) {
2248
+ throw new Error("Authenticated admin user is missing an email address.");
2249
+ }
2250
+ const token = await signCollectionToken({
2251
+ sub: user.id,
2252
+ email: user.email,
2253
+ collection: adminCollection.slug,
2254
+ providerId: provider.id,
2255
+ authSource: "external"
2256
+ });
2257
+ return {
2258
+ token,
2259
+ collectionSlug: adminCollection.slug,
2260
+ returnTo: resolved.returnTo
2261
+ };
2262
+ }
2263
+ async resolveAccess(identity, providerId, siteId, c, user) {
2264
+ const resolved = await this.config.adminAuth?.resolveAccess?.({
2265
+ identity,
2266
+ providerId,
2267
+ siteId,
2268
+ workspaceId: c.get("workspaceId"),
2269
+ req: {
2270
+ method: c.req.method,
2271
+ path: c.req.path,
2272
+ url: c.req.url,
2273
+ headers: Object.fromEntries(c.req.raw.headers.entries())
2274
+ },
2275
+ user
2276
+ });
2277
+ if (resolved) return resolved;
2278
+ return { allowed: true, roles: identity.roles, data: {} };
2279
+ }
2280
+ async buildUserData(identity, providerId, roles, extraData) {
2281
+ return {
2282
+ ...identity.email ? { email: identity.email } : {},
2283
+ ...identity.name ? { name: identity.name } : {},
2284
+ ...roles ? { roles } : {},
2285
+ ...extraData ?? {},
2286
+ authProvider: providerId,
2287
+ externalSubject: identity.sub,
2288
+ authSource: "external",
2289
+ lastLoginAt: (/* @__PURE__ */ new Date()).toISOString()
2290
+ };
2291
+ }
2292
+ async findUserByExternalIdentity(db, collection, providerId, externalSubject) {
2293
+ const result = await db.find({
2294
+ collection,
2295
+ where: {
2296
+ authProvider: providerId,
2297
+ externalSubject
2298
+ },
2299
+ limit: 1
2300
+ });
2301
+ return result.docs[0] ?? null;
2302
+ }
2303
+ async buildOidcStartUrl(provider, returnTo, siteId, origin) {
2304
+ const discovery = await this.getOidcDiscovery(provider);
2305
+ const nonce = (0, import_node_crypto2.randomBytes)(16).toString("hex");
2306
+ const state = await this.signState({
2307
+ providerId: provider.id,
2308
+ returnTo,
2309
+ siteId,
2310
+ nonce
2311
+ });
2312
+ const url = new URL(provider.authorizationEndpoint || discovery.authorization_endpoint);
2313
+ url.searchParams.set("response_type", "code");
2314
+ url.searchParams.set("client_id", provider.clientId);
2315
+ url.searchParams.set("redirect_uri", provider.redirectUri || this.defaultCallbackPath(provider.id, origin));
2316
+ url.searchParams.set("scope", (provider.scopes ?? ["openid", "profile", "email"]).join(" "));
2317
+ url.searchParams.set("state", state);
2318
+ url.searchParams.set("nonce", nonce);
2319
+ return url.toString();
2320
+ }
2321
+ async resolveOidcIdentity(provider, c) {
2322
+ const code = c.req.query("code");
2323
+ const state = c.req.query("state");
2324
+ if (!code || !state) throw new Error("Missing OIDC callback parameters.");
2325
+ const parsedState = await this.verifyState(state, provider.id);
2326
+ const discovery = await this.getOidcDiscovery(provider);
2327
+ const redirectUri = provider.redirectUri || this.defaultCallbackPath(provider.id, new URL(c.req.url).origin);
2328
+ const tokenResponse = await fetch(provider.tokenEndpoint || discovery.token_endpoint, {
2329
+ method: "POST",
2330
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2331
+ body: new URLSearchParams({
2332
+ grant_type: "authorization_code",
2333
+ code,
2334
+ redirect_uri: redirectUri,
2335
+ client_id: provider.clientId,
2336
+ client_secret: provider.clientSecret
2337
+ })
2338
+ });
2339
+ if (!tokenResponse.ok) {
2340
+ throw new Error("OIDC token exchange failed.");
2341
+ }
2342
+ const tokenBody = await tokenResponse.json();
2343
+ const idToken = tokenBody.id_token;
2344
+ let claims = {};
2345
+ if (idToken) {
2346
+ const jwks = (0, import_jose2.createRemoteJWKSet)(new URL(discovery.jwks_uri));
2347
+ const { payload } = await (0, import_jose2.jwtVerify)(idToken, jwks, {
2348
+ issuer: provider.issuer,
2349
+ audience: provider.clientId
2350
+ });
2351
+ if (parsedState.nonce && payload.nonce !== parsedState.nonce) {
2352
+ throw new Error("OIDC nonce verification failed.");
2353
+ }
2354
+ claims = payload;
2355
+ } else if (tokenBody.access_token && (provider.userInfoEndpoint || discovery.userinfo_endpoint)) {
2356
+ const userInfo = await fetch(provider.userInfoEndpoint || discovery.userinfo_endpoint, {
2357
+ headers: { Authorization: `Bearer ${tokenBody.access_token}` }
2358
+ });
2359
+ if (!userInfo.ok) throw new Error("OIDC userinfo request failed.");
2360
+ claims = await userInfo.json();
2361
+ } else {
2362
+ throw new Error("OIDC provider did not return usable identity claims.");
2363
+ }
2364
+ return {
2365
+ identity: this.mapClaims(claims, provider.claimMapping),
2366
+ returnTo: parsedState.returnTo
2367
+ };
2368
+ }
2369
+ async resolveCustomIdentity(provider, c) {
2370
+ const body = c.req.method === "POST" ? await c.req.json().catch(() => ({})) : {};
2371
+ const tokenParam = provider.tokenParam || "token";
2372
+ const token = body?.[tokenParam] || c.req.query(tokenParam);
2373
+ if (!token) {
2374
+ throw new Error(`Missing ${tokenParam} for external auth exchange.`);
2375
+ }
2376
+ let claims;
2377
+ if (provider.secret) {
2378
+ const secret = new import_node_util3.TextEncoder().encode(provider.secret);
2379
+ const verified = await (0, import_jose2.jwtVerify)(token, secret, {
2380
+ issuer: provider.issuer,
2381
+ audience: provider.audience
2382
+ });
2383
+ claims = verified.payload;
2384
+ } else if (provider.jwksUri) {
2385
+ const jwks = (0, import_jose2.createRemoteJWKSet)(new URL(provider.jwksUri));
2386
+ const verified = await (0, import_jose2.jwtVerify)(token, jwks, {
2387
+ issuer: provider.issuer,
2388
+ audience: provider.audience
2389
+ });
2390
+ claims = verified.payload;
2391
+ } else {
2392
+ claims = (0, import_jose2.decodeJwt)(token);
2393
+ if (provider.issuer && claims.iss !== provider.issuer) {
2394
+ throw new Error("External auth issuer mismatch.");
2395
+ }
2396
+ if (provider.audience) {
2397
+ const aud = claims.aud;
2398
+ const matches = Array.isArray(aud) ? aud.includes(provider.audience) : aud === provider.audience;
2399
+ if (!matches) throw new Error("External auth audience mismatch.");
2400
+ }
2401
+ }
2402
+ return {
2403
+ identity: this.mapClaims(claims, provider.claimMapping),
2404
+ returnTo: this.normalizeReturnTo(
2405
+ body?.returnTo || c.req.query("returnTo"),
2406
+ c
2407
+ )
2408
+ };
2409
+ }
2410
+ mapClaims(claims, mapping) {
2411
+ const subKey = mapping?.sub || "sub";
2412
+ const emailKey = mapping?.email || "email";
2413
+ const nameKey = mapping?.name || "name";
2414
+ const rolesKey = mapping?.roles;
2415
+ const groupsKey = mapping?.groups;
2416
+ const siteIdsKey = mapping?.siteIds;
2417
+ const workspaceIdsKey = mapping?.workspaceIds;
2418
+ const sub = claims[subKey];
2419
+ if (typeof sub !== "string" || !sub) {
2420
+ throw new Error("External identity is missing a subject.");
2421
+ }
2422
+ return {
2423
+ sub,
2424
+ email: this.readStringClaim(claims[emailKey]),
2425
+ name: this.readStringClaim(claims[nameKey]),
2426
+ roles: this.readStringArrayClaim(rolesKey ? claims[rolesKey] : void 0),
2427
+ groups: this.readStringArrayClaim(groupsKey ? claims[groupsKey] : void 0),
2428
+ siteIds: this.readStringArrayClaim(siteIdsKey ? claims[siteIdsKey] : void 0),
2429
+ workspaceIds: this.readStringArrayClaim(workspaceIdsKey ? claims[workspaceIdsKey] : void 0),
2430
+ rawClaims: claims
2431
+ };
2432
+ }
2433
+ readStringClaim(value) {
2434
+ return typeof value === "string" ? value : void 0;
2435
+ }
2436
+ readStringArrayClaim(value) {
2437
+ if (!value) return void 0;
2438
+ if (Array.isArray(value)) {
2439
+ return value.filter((entry) => typeof entry === "string");
2440
+ }
2441
+ if (typeof value === "string") return [value];
2442
+ return void 0;
2443
+ }
2444
+ async getOidcDiscovery(provider) {
2445
+ const discoveryUrl = new URL(
2446
+ provider.issuer.replace(/\/$/, "") + "/.well-known/openid-configuration"
2447
+ );
2448
+ const response = await fetch(discoveryUrl.toString());
2449
+ if (!response.ok) throw new Error("Failed to load OIDC discovery document.");
2450
+ return response.json();
2451
+ }
2452
+ defaultCallbackPath(providerId, origin = "") {
2453
+ return `${origin}/api/admin/auth/${providerId}/callback`;
2454
+ }
2455
+ async signState(payload) {
2456
+ return new import_jose2.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime("15m").sign(this.getStateSecret());
2457
+ }
2458
+ async verifyState(token, providerId) {
2459
+ const { payload } = await (0, import_jose2.jwtVerify)(token, this.getStateSecret());
2460
+ const state = payload;
2461
+ if (state.providerId !== providerId) {
2462
+ throw new Error("OIDC state verification failed.");
2463
+ }
2464
+ return state;
2465
+ }
2466
+ getStateSecret() {
2467
+ const secret = process.env.DYRECTED_JWT_SECRET || process.env.JWT_SECRET;
2468
+ if (!secret) {
2469
+ throw new Error("[dyrected/core] DYRECTED_JWT_SECRET is not set.");
2470
+ }
2471
+ return new import_node_util3.TextEncoder().encode(secret);
2472
+ }
2473
+ normalizeReturnTo(returnTo, c) {
2474
+ if (returnTo) return returnTo;
2475
+ const url = new URL(c.req.url);
2476
+ return `${url.origin}/admin`;
2477
+ }
2478
+ };
2479
+
2480
+ // src/controllers/preview.controller.ts
2481
+ var import_jose3 = require("jose");
2482
+ var import_node_util4 = require("util");
2096
2483
  var PreviewController = class {
2097
2484
  getSecret() {
2098
2485
  const secret = process.env.DYRECTED_JWT_SECRET || process.env.JWT_SECRET || "dyrected-preview-secret-change-me";
2099
- return new import_node_util3.TextEncoder().encode(secret);
2486
+ return new import_node_util4.TextEncoder().encode(secret);
2100
2487
  }
2101
2488
  /**
2102
2489
  * POST /api/preview-token
@@ -2107,7 +2494,7 @@ var PreviewController = class {
2107
2494
  if (!body?.collectionSlug || !body?.data) {
2108
2495
  return c.json({ error: true, message: "collectionSlug and data are required." }, 400);
2109
2496
  }
2110
- const token = await new import_jose2.SignJWT({
2497
+ const token = await new import_jose3.SignJWT({
2111
2498
  collectionSlug: body.collectionSlug,
2112
2499
  documentId: body.documentId,
2113
2500
  data: body.data
@@ -2125,7 +2512,7 @@ var PreviewController = class {
2125
2512
  return c.json({ error: true, message: "token query parameter is required." }, 400);
2126
2513
  }
2127
2514
  try {
2128
- const { payload } = await (0, import_jose2.jwtVerify)(token, this.getSecret());
2515
+ const { payload } = await (0, import_jose3.jwtVerify)(token, this.getSecret());
2129
2516
  return c.json(payload);
2130
2517
  } catch (err) {
2131
2518
  return c.json({ error: true, message: "Invalid or expired preview token." }, 401);
@@ -3110,6 +3497,9 @@ function registerRoutes(app, config) {
3110
3497
  if (dynamic.admin) {
3111
3498
  config.admin = { ...config.admin, ...dynamic.admin };
3112
3499
  }
3500
+ if (dynamic.adminAuth) {
3501
+ config.adminAuth = { ...config.adminAuth, ...dynamic.adminAuth };
3502
+ }
3113
3503
  }
3114
3504
  const user = c.get("user");
3115
3505
  const accessArgs = { user, req: c.req, doc: null };
@@ -3198,7 +3588,8 @@ function registerRoutes(app, config) {
3198
3588
  return c.json({
3199
3589
  collections: filteredCollections,
3200
3590
  globals: filteredGlobals,
3201
- admin: config.admin || {}
3591
+ admin: config.admin || {},
3592
+ adminAuth: getPublicAdminAuthConfig(config.adminAuth)
3202
3593
  });
3203
3594
  });
3204
3595
  app.get("/api/dyrected/options/:collection/:field", optionalAuth(), async (c) => {
@@ -3390,6 +3781,12 @@ function registerRoutes(app, config) {
3390
3781
  app.delete(`${prefix}/media/:id`, accessGate(col, "delete"), (c) => mediaController.delete(c));
3391
3782
  }
3392
3783
  }
3784
+ const adminAuthController = new AdminAuthController(config);
3785
+ app.get("/api/admin/auth/providers", (c) => adminAuthController.providers(c));
3786
+ app.get("/api/admin/auth/:provider/start", (c) => adminAuthController.start(c));
3787
+ app.get("/api/admin/auth/:provider/callback", (c) => adminAuthController.callback(c));
3788
+ app.post("/api/admin/auth/:provider/exchange", (c) => adminAuthController.exchange(c));
3789
+ app.post("/api/admin/logout", (c) => adminAuthController.logout(c));
3393
3790
  for (const collection of config.collections) {
3394
3791
  if (!collection.auth) continue;
3395
3792
  const path = `/api/collections/${collection.slug}`;
@@ -3625,6 +4022,10 @@ function normalizeConfig(config) {
3625
4022
  const globals = config?.globals || [];
3626
4023
  const needsAudit = collections.some((col) => col.audit);
3627
4024
  const needsWorkflow = collections.some((col) => col.workflow);
4025
+ const adminAuthCollectionSlug = getAdminAuthCollection({
4026
+ collections,
4027
+ adminAuth: config.adminAuth
4028
+ })?.slug;
3628
4029
  const normalizedCollections = collections.map((col) => {
3629
4030
  let fields = col.fields || [];
3630
4031
  const existingFieldNames = new Set(fields.map((f) => f.name));
@@ -3711,6 +4112,39 @@ function normalizeConfig(config) {
3711
4112
  return field;
3712
4113
  });
3713
4114
  }
4115
+ if (adminAuthCollectionSlug && col.slug === adminAuthCollectionSlug && config.adminAuth?.mode === "external") {
4116
+ const externalAdminFields = [
4117
+ {
4118
+ name: "authProvider",
4119
+ type: "text",
4120
+ label: "Auth Provider",
4121
+ admin: { readOnly: true, hidden: true }
4122
+ },
4123
+ {
4124
+ name: "externalSubject",
4125
+ type: "text",
4126
+ label: "External Subject",
4127
+ admin: { readOnly: true, hidden: true }
4128
+ },
4129
+ {
4130
+ name: "authSource",
4131
+ type: "text",
4132
+ label: "Auth Source",
4133
+ admin: { readOnly: true, hidden: true }
4134
+ },
4135
+ {
4136
+ name: "lastLoginAt",
4137
+ type: "date",
4138
+ label: "Last Login At",
4139
+ admin: { readOnly: true, hidden: true }
4140
+ }
4141
+ ];
4142
+ for (const field of externalAdminFields) {
4143
+ if (!existingFieldNames.has(field.name)) {
4144
+ fields = [...fields, field];
4145
+ }
4146
+ }
4147
+ }
3714
4148
  const updatedFieldNames = new Set(fields.map((f) => f.name));
3715
4149
  const fieldsToInject = SYSTEM_FIELDS.filter((f) => !updatedFieldNames.has(f.name));
3716
4150
  return {
package/dist/server.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as hono_types from 'hono/types';
2
2
  import * as hono from 'hono';
3
3
  import { Hono, Context } from 'hono';
4
- import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './index-CdQwnbfh.cjs';
4
+ import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './index-DfkPT4NR.cjs';
5
5
  import * as hono_utils_http_status from 'hono/utils/http-status';
6
6
  import * as hono_utils_types from 'hono/utils/types';
7
7
  import 'lucide-react';
package/dist/server.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as hono_types from 'hono/types';
2
2
  import * as hono from 'hono';
3
3
  import { Hono, Context } from 'hono';
4
- import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './index-CdQwnbfh.js';
4
+ import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './index-DfkPT4NR.js';
5
5
  import * as hono_utils_http_status from 'hono/utils/http-status';
6
6
  import * as hono_utils_types from 'hono/utils/types';
7
7
  import 'lucide-react';