feat: add auth/access foundation - deps, DB schema, shared types, and config
Add Better Auth, drizzle-orm, @dnd-kit, and remark-gfm dependencies. Introduce DB schema for auth tables (user, session, account, verification), company memberships, instance user roles, permission grants, invites, and join requests. Add assigneeUserId to issues. Extend shared config schema with deployment mode/exposure/auth settings, add access types and validators, and wire up new API path constants. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
47
packages/db/src/schema/auth.ts
Normal file
47
packages/db/src/schema/auth.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core";
|
||||
|
||||
export const authUsers = pgTable("user", {
|
||||
id: text("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
email: text("email").notNull(),
|
||||
emailVerified: boolean("email_verified").notNull().default(false),
|
||||
image: text("image"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull(),
|
||||
});
|
||||
|
||||
export const authSessions = pgTable("session", {
|
||||
id: text("id").primaryKey(),
|
||||
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
||||
token: text("token").notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull(),
|
||||
ipAddress: text("ip_address"),
|
||||
userAgent: text("user_agent"),
|
||||
userId: text("user_id").notNull().references(() => authUsers.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const authAccounts = pgTable("account", {
|
||||
id: text("id").primaryKey(),
|
||||
accountId: text("account_id").notNull(),
|
||||
providerId: text("provider_id").notNull(),
|
||||
userId: text("user_id").notNull().references(() => authUsers.id, { onDelete: "cascade" }),
|
||||
accessToken: text("access_token"),
|
||||
refreshToken: text("refresh_token"),
|
||||
idToken: text("id_token"),
|
||||
accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true }),
|
||||
refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { withTimezone: true }),
|
||||
scope: text("scope"),
|
||||
password: text("password"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull(),
|
||||
});
|
||||
|
||||
export const authVerifications = pgTable("verification", {
|
||||
id: text("id").primaryKey(),
|
||||
identifier: text("identifier").notNull(),
|
||||
value: text("value").notNull(),
|
||||
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }),
|
||||
});
|
||||
29
packages/db/src/schema/company_memberships.ts
Normal file
29
packages/db/src/schema/company_memberships.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { pgTable, uuid, text, timestamp, uniqueIndex, index } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
export const companyMemberships = pgTable(
|
||||
"company_memberships",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
principalType: text("principal_type").notNull(),
|
||||
principalId: text("principal_id").notNull(),
|
||||
status: text("status").notNull().default("active"),
|
||||
membershipRole: text("membership_role"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyPrincipalUniqueIdx: uniqueIndex("company_memberships_company_principal_unique_idx").on(
|
||||
table.companyId,
|
||||
table.principalType,
|
||||
table.principalId,
|
||||
),
|
||||
principalStatusIdx: index("company_memberships_principal_status_idx").on(
|
||||
table.principalType,
|
||||
table.principalId,
|
||||
table.status,
|
||||
),
|
||||
companyStatusIdx: index("company_memberships_company_status_idx").on(table.companyId, table.status),
|
||||
}),
|
||||
);
|
||||
@@ -1,5 +1,11 @@
|
||||
export { companies } from "./companies.js";
|
||||
export { authUsers, authSessions, authAccounts, authVerifications } from "./auth.js";
|
||||
export { instanceUserRoles } from "./instance_user_roles.js";
|
||||
export { agents } from "./agents.js";
|
||||
export { companyMemberships } from "./company_memberships.js";
|
||||
export { principalPermissionGrants } from "./principal_permission_grants.js";
|
||||
export { invites } from "./invites.js";
|
||||
export { joinRequests } from "./join_requests.js";
|
||||
export { agentConfigRevisions } from "./agent_config_revisions.js";
|
||||
export { agentApiKeys } from "./agent_api_keys.js";
|
||||
export { agentRuntimeState } from "./agent_runtime_state.js";
|
||||
|
||||
16
packages/db/src/schema/instance_user_roles.ts
Normal file
16
packages/db/src/schema/instance_user_roles.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { pgTable, uuid, text, timestamp, uniqueIndex, index } from "drizzle-orm/pg-core";
|
||||
|
||||
export const instanceUserRoles = pgTable(
|
||||
"instance_user_roles",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: text("user_id").notNull(),
|
||||
role: text("role").notNull().default("instance_admin"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
userRoleUniqueIdx: uniqueIndex("instance_user_roles_user_role_unique_idx").on(table.userId, table.role),
|
||||
roleIdx: index("instance_user_roles_role_idx").on(table.role),
|
||||
}),
|
||||
);
|
||||
29
packages/db/src/schema/invites.ts
Normal file
29
packages/db/src/schema/invites.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { pgTable, uuid, text, timestamp, jsonb, index, uniqueIndex } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
export const invites = pgTable(
|
||||
"invites",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").references(() => companies.id),
|
||||
inviteType: text("invite_type").notNull().default("company_join"),
|
||||
tokenHash: text("token_hash").notNull(),
|
||||
allowedJoinTypes: text("allowed_join_types").notNull().default("both"),
|
||||
defaultsPayload: jsonb("defaults_payload").$type<Record<string, unknown> | null>(),
|
||||
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
||||
invitedByUserId: text("invited_by_user_id"),
|
||||
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
||||
acceptedAt: timestamp("accepted_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
tokenHashUniqueIdx: uniqueIndex("invites_token_hash_unique_idx").on(table.tokenHash),
|
||||
companyInviteStateIdx: index("invites_company_invite_state_idx").on(
|
||||
table.companyId,
|
||||
table.inviteType,
|
||||
table.revokedAt,
|
||||
table.expiresAt,
|
||||
),
|
||||
}),
|
||||
);
|
||||
@@ -27,6 +27,7 @@ export const issues = pgTable(
|
||||
status: text("status").notNull().default("backlog"),
|
||||
priority: text("priority").notNull().default("medium"),
|
||||
assigneeAgentId: uuid("assignee_agent_id").references(() => agents.id),
|
||||
assigneeUserId: text("assignee_user_id"),
|
||||
checkoutRunId: uuid("checkout_run_id").references(() => heartbeatRuns.id, { onDelete: "set null" }),
|
||||
executionRunId: uuid("execution_run_id").references(() => heartbeatRuns.id, { onDelete: "set null" }),
|
||||
executionAgentNameKey: text("execution_agent_name_key"),
|
||||
@@ -51,6 +52,11 @@ export const issues = pgTable(
|
||||
table.assigneeAgentId,
|
||||
table.status,
|
||||
),
|
||||
assigneeUserStatusIdx: index("issues_company_assignee_user_status_idx").on(
|
||||
table.companyId,
|
||||
table.assigneeUserId,
|
||||
table.status,
|
||||
),
|
||||
parentIdx: index("issues_company_parent_idx").on(table.companyId, table.parentId),
|
||||
projectIdx: index("issues_company_project_idx").on(table.companyId, table.projectId),
|
||||
identifierIdx: uniqueIndex("issues_company_identifier_idx").on(table.companyId, table.identifier),
|
||||
|
||||
38
packages/db/src/schema/join_requests.ts
Normal file
38
packages/db/src/schema/join_requests.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { pgTable, uuid, text, timestamp, jsonb, index, uniqueIndex } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
import { invites } from "./invites.js";
|
||||
import { agents } from "./agents.js";
|
||||
|
||||
export const joinRequests = pgTable(
|
||||
"join_requests",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
inviteId: uuid("invite_id").notNull().references(() => invites.id),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
requestType: text("request_type").notNull(),
|
||||
status: text("status").notNull().default("pending_approval"),
|
||||
requestIp: text("request_ip").notNull(),
|
||||
requestingUserId: text("requesting_user_id"),
|
||||
requestEmailSnapshot: text("request_email_snapshot"),
|
||||
agentName: text("agent_name"),
|
||||
adapterType: text("adapter_type"),
|
||||
capabilities: text("capabilities"),
|
||||
agentDefaultsPayload: jsonb("agent_defaults_payload").$type<Record<string, unknown> | null>(),
|
||||
createdAgentId: uuid("created_agent_id").references(() => agents.id),
|
||||
approvedByUserId: text("approved_by_user_id"),
|
||||
approvedAt: timestamp("approved_at", { withTimezone: true }),
|
||||
rejectedByUserId: text("rejected_by_user_id"),
|
||||
rejectedAt: timestamp("rejected_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
inviteUniqueIdx: uniqueIndex("join_requests_invite_unique_idx").on(table.inviteId),
|
||||
companyStatusTypeCreatedIdx: index("join_requests_company_status_type_created_idx").on(
|
||||
table.companyId,
|
||||
table.status,
|
||||
table.requestType,
|
||||
table.createdAt,
|
||||
),
|
||||
}),
|
||||
);
|
||||
29
packages/db/src/schema/principal_permission_grants.ts
Normal file
29
packages/db/src/schema/principal_permission_grants.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { pgTable, uuid, text, timestamp, jsonb, uniqueIndex, index } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
export const principalPermissionGrants = pgTable(
|
||||
"principal_permission_grants",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
principalType: text("principal_type").notNull(),
|
||||
principalId: text("principal_id").notNull(),
|
||||
permissionKey: text("permission_key").notNull(),
|
||||
scope: jsonb("scope").$type<Record<string, unknown> | null>(),
|
||||
grantedByUserId: text("granted_by_user_id"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
uniqueGrantIdx: uniqueIndex("principal_permission_grants_unique_idx").on(
|
||||
table.companyId,
|
||||
table.principalType,
|
||||
table.principalId,
|
||||
table.permissionKey,
|
||||
),
|
||||
companyPermissionIdx: index("principal_permission_grants_company_permission_idx").on(
|
||||
table.companyId,
|
||||
table.permissionKey,
|
||||
),
|
||||
}),
|
||||
);
|
||||
Reference in New Issue
Block a user