Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/platform-objects/src/identity/sys-user.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,19 @@ export const SysUser = ObjectSchema.create({
description: 'When set, the ban auto-clears at this time.',
}),

ai_access: Field.boolean({
label: 'AI Access',
defaultValue: false,
group: 'Admin',
description:
'Whether this user holds an AI seat — grants access to the in-UI AI ' +
'agents (build / ask). The framework synthesizes the `ai_seat` ' +
'capability from this flag (plugin-hono-server resolveCtx). Assignment ' +
'is capped by the licensed / purchased seat count (enforced by ' +
'@objectstack/security-enterprise AiSeatPlugin). Owned by objectql ' +
'(better-auth is oblivious to this column).',
}),

// ── Profile ──────────────────────────────────────────────────
image: Field.url({
label: 'Profile Image',
Expand Down
8 changes: 8 additions & 0 deletions packages/plugins/plugin-auth/src/auth-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,14 @@ export class AuthManager {
// createAdapterFactory.
user: {
...AUTH_USER_CONFIG,
// NOTE: the env-side AI-seat marker `sys_user.ai_access` is deliberately
// NOT declared as a better-auth additionalField. sys_user is a
// better-auth-MANAGED table and better-auth SELECTs explicit columns, so
// declaring it here would make getSession query a column that may not
// exist on every env yet → broken auth. Instead the column is owned by
// the objectql `SysUser` object def (provisioned by boot schema-sync)
// and read by a GUARDED system query in resolveCtx (can only no-op,
// never break auth). better-auth stays oblivious to the extra column.
},
account: {
...AUTH_ACCOUNT_CONFIG,
Expand Down
24 changes: 24 additions & 0 deletions packages/plugins/plugin-hono-server/src/hono-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,30 @@ export class HonoServerPlugin implements Plugin {
/* fall back to self-only */
}
}
// Env-side AI-seat marker (simple model). The single-org env
// DB has no permission-set/org dimension for this — the seat is
// the boolean `sys_user.ai_access`. Read it with a GUARDED system
// query (NOT a better-auth additionalField: sys_user is
// better-auth-managed and better-auth SELECTs explicit columns,
// so an additionalField would make getSession query a possibly-
// missing column → broken auth; a guarded read can only no-op).
// When true, synthesize the `ai_seat` capability so the per-agent
// gate (evaluateAgentAccess → requires `ai_seat`) admits the user
// with no permission-set grant. Absent/false/missing-column →
// no synthesis (deny, as before).
if (!permissions.includes('ai_seat')) {
try {
const ql = getObjectQL();
const sysCtx = { context: { isSystem: true } };
const uRows = await ql?.find?.(
'sys_user',
{ where: { id: userId }, limit: 1, ...sysCtx } as any,
).catch(() => []);
if ((uRows?.[0] as any)?.ai_access === true) permissions.push('ai_seat');
} catch {
/* no ai_access column / query failed → no seat (safe) */
}
}
return {
userId,
tenantId,
Expand Down