remove the dead multi-user surface
Officer is single-user: the server owner is the only account, created once by /auth/bootstrap. Everything that existed to serve additional users was unreachable, so it is gone rather than left looking like it does something. Accounts: drop the invite / resend-invite / delete / list-users routes and the Users settings screen, the inert /auth/signup handler, and the account verification chain it fed (verify, resend-verification, VerifyScreen, the UserInvite + VerifyAdmin + VerifyRegistration templates). /auth/verify-token survives for password resets only, and now requires a reset-password token rather than accepting any signed JWT. Roles: drop the users.role column and the four-value USER_ROLES enum. The permissions table granted every role identical methods, and every role === 'Super Admin' check was permanently true. The JWT no longer carries a role claim. Sandbox: remove sidecar/sandbox.ts and its five call sites. bwrap was selected only for non-Super-Admin users, so it never ran. It was also not a usable agent jail as written — --share-net, the project root (with .env) bound read-only, and runuser dropping to the server's own uid. Rebuilding it for agent containment would be a different construction, and git history keeps this one. getHomeDir keeps its DATA_PATH meaning; the new getOwnerHomeDir resolves the owner's real login home, which is what terminals, chats and task runs use. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
92de996412
commit
044aacf4d5
@@ -0,0 +1 @@
|
||||
ALTER TABLE "users" DROP COLUMN "role";
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,13 @@
|
||||
"when": 1784813792772,
|
||||
"tag": "0005_small_the_phantom",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1785013713432,
|
||||
"tag": "0006_absurd_dormammu",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -15,7 +15,11 @@ export async function getServerIntegration(provider: string): Promise<ServerInte
|
||||
return row;
|
||||
}
|
||||
|
||||
export async function upsertServerIntegration(provider: string, config: Record<string, unknown>, enabled = true): Promise<ServerIntegrationSelect> {
|
||||
export async function upsertServerIntegration(
|
||||
provider: string,
|
||||
config: Record<string, unknown>,
|
||||
enabled = true,
|
||||
): Promise<ServerIntegrationSelect> {
|
||||
const [row] = await db
|
||||
.insert(serverIntegrations)
|
||||
.values({ provider, config, enabled, updatedAt: new Date() })
|
||||
@@ -28,7 +32,10 @@ export async function upsertServerIntegration(provider: string, config: Record<s
|
||||
}
|
||||
|
||||
export async function deleteServerIntegration(provider: string): Promise<boolean> {
|
||||
const result = await db.delete(serverIntegrations).where(eq(serverIntegrations.provider, provider)).returning({ id: serverIntegrations.id });
|
||||
const result = await db
|
||||
.delete(serverIntegrations)
|
||||
.where(eq(serverIntegrations.provider, provider))
|
||||
.returning({ id: serverIntegrations.id });
|
||||
return result.length > 0;
|
||||
}
|
||||
|
||||
@@ -57,7 +64,12 @@ type UpsertUserIntegrationParams = {
|
||||
config: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export async function upsertUserIntegration({ userId, provider, serverIntegrationId, config }: UpsertUserIntegrationParams): Promise<UserIntegrationSelect> {
|
||||
export async function upsertUserIntegration({
|
||||
userId,
|
||||
provider,
|
||||
serverIntegrationId,
|
||||
config,
|
||||
}: UpsertUserIntegrationParams): Promise<UserIntegrationSelect> {
|
||||
const [row] = await db
|
||||
.insert(userIntegrations)
|
||||
.values({ userId, provider, serverIntegrationId: serverIntegrationId ?? null, config, updatedAt: new Date() })
|
||||
@@ -80,7 +92,7 @@ export async function deleteUserIntegration(userId: number, provider: string): P
|
||||
// ── Cross-table lookup ──
|
||||
|
||||
type UserIntegrationWithUser = UserIntegrationSelect & {
|
||||
user: { id: number; email: string; username: string | null; role: string };
|
||||
user: { id: number; email: string; username: string | null };
|
||||
};
|
||||
|
||||
export async function findUserByIntegrationConfig(
|
||||
@@ -101,16 +113,12 @@ export async function findUserByIntegrationConfig(
|
||||
id: users.id,
|
||||
email: users.email,
|
||||
username: users.username,
|
||||
role: users.role,
|
||||
},
|
||||
})
|
||||
.from(userIntegrations)
|
||||
.innerJoin(users, eq(userIntegrations.userId, users.id))
|
||||
.where(
|
||||
and(
|
||||
eq(userIntegrations.provider, provider),
|
||||
sql`${userIntegrations.config}->>${configKey} = ${configValue}`,
|
||||
),
|
||||
and(eq(userIntegrations.provider, provider), sql`${userIntegrations.config}->>${configKey} = ${configValue}`),
|
||||
);
|
||||
return row as UserIntegrationWithUser | undefined;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ export const users = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
email: text('email').notNull().unique(),
|
||||
password: text('password'),
|
||||
role: text('role', { enum: ['Member', 'Admin', 'Owner', 'Super Admin'] }).notNull().default('Member'),
|
||||
status: text('status', { enum: ['Unverified', 'Active', 'Prospect', 'Invited', 'Blocked', 'Banned', 'Deleted'] }).notNull().default('Unverified'),
|
||||
status: text('status', { enum: ['Unverified', 'Active', 'Prospect', 'Invited', 'Blocked', 'Banned', 'Deleted'] })
|
||||
.notNull()
|
||||
.default('Unverified'),
|
||||
name: text('name'),
|
||||
username: text('username').unique(),
|
||||
avatar: text('avatar'),
|
||||
@@ -16,7 +17,9 @@ export const users = pgTable('users', {
|
||||
|
||||
export const passkeys = pgTable('passkeys', {
|
||||
id: serial('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
userId: integer('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
origin: text('origin'),
|
||||
credentialId: text('credential_id'),
|
||||
publicKey: text('public_key'),
|
||||
@@ -26,16 +29,20 @@ export const passkeys = pgTable('passkeys', {
|
||||
|
||||
export const passkeyChallenges = pgTable('passkey_challenges', {
|
||||
id: serial('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
userId: integer('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
origin: text('origin').notNull(),
|
||||
challenge: text('challenge').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
||||
});
|
||||
|
||||
export const tokenBlacklist = pgTable('token_blacklist', {
|
||||
jti: text('jti').primaryKey(),
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
||||
}, (table) => [
|
||||
index('idx_token_blacklist_expires').on(table.expiresAt),
|
||||
]);
|
||||
export const tokenBlacklist = pgTable(
|
||||
'token_blacklist',
|
||||
{
|
||||
jti: text('jti').primaryKey(),
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
||||
},
|
||||
(table) => [index('idx_token_blacklist_expires').on(table.expiresAt)],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user