feat: join request claim secrets, onboarding API, and company branding
Add secure claim secret flow for agent join requests with timing-safe comparison, expiry, and one-time use. Expose machine-readable onboarding manifests and skill index API endpoints. Add company brand color with hex validation, pattern icon generation, and settings page integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1
packages/db/src/migrations/0022_company_brand_color.sql
Normal file
1
packages/db/src/migrations/0022_company_brand_color.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "companies" ADD COLUMN "brand_color" text;
|
||||
3
packages/db/src/migrations/0023_fair_lethal_legion.sql
Normal file
3
packages/db/src/migrations/0023_fair_lethal_legion.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "join_requests" ADD COLUMN "claim_secret_hash" text;--> statement-breakpoint
|
||||
ALTER TABLE "join_requests" ADD COLUMN "claim_secret_expires_at" timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "join_requests" ADD COLUMN "claim_secret_consumed_at" timestamp with time zone;
|
||||
5633
packages/db/src/migrations/meta/0023_snapshot.json
Normal file
5633
packages/db/src/migrations/meta/0023_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -155,6 +155,20 @@
|
||||
"when": 1772122471656,
|
||||
"tag": "0021_chief_vindicator",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 22,
|
||||
"version": "7",
|
||||
"when": 1772186400000,
|
||||
"tag": "0022_company_brand_color",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 23,
|
||||
"version": "7",
|
||||
"when": 1772139727599,
|
||||
"tag": "0023_fair_lethal_legion",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,6 +14,7 @@ export const companies = pgTable(
|
||||
requireBoardApprovalForNewAgents: boolean("require_board_approval_for_new_agents")
|
||||
.notNull()
|
||||
.default(true),
|
||||
brandColor: text("brand_color"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
|
||||
@@ -18,6 +18,9 @@ export const joinRequests = pgTable(
|
||||
adapterType: text("adapter_type"),
|
||||
capabilities: text("capabilities"),
|
||||
agentDefaultsPayload: jsonb("agent_defaults_payload").$type<Record<string, unknown> | null>(),
|
||||
claimSecretHash: text("claim_secret_hash"),
|
||||
claimSecretExpiresAt: timestamp("claim_secret_expires_at", { withTimezone: true }),
|
||||
claimSecretConsumedAt: timestamp("claim_secret_consumed_at", { withTimezone: true }),
|
||||
createdAgentId: uuid("created_agent_id").references(() => agents.id),
|
||||
approvedByUserId: text("approved_by_user_id"),
|
||||
approvedAt: timestamp("approved_at", { withTimezone: true }),
|
||||
|
||||
@@ -181,6 +181,7 @@ export {
|
||||
createCompanyInviteSchema,
|
||||
acceptInviteSchema,
|
||||
listJoinRequestsQuerySchema,
|
||||
claimJoinRequestApiKeySchema,
|
||||
updateMemberPermissionsSchema,
|
||||
updateUserCompanyAccessSchema,
|
||||
type CreateCostEvent,
|
||||
@@ -189,6 +190,7 @@ export {
|
||||
type CreateCompanyInvite,
|
||||
type AcceptInvite,
|
||||
type ListJoinRequestsQuery,
|
||||
type ClaimJoinRequestApiKey,
|
||||
type UpdateMemberPermissions,
|
||||
type UpdateUserCompanyAccess,
|
||||
} from "./validators/index.js";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
AgentAdapterType,
|
||||
InstanceUserRole,
|
||||
InviteJoinType,
|
||||
InviteType,
|
||||
@@ -57,9 +58,11 @@ export interface JoinRequest {
|
||||
requestingUserId: string | null;
|
||||
requestEmailSnapshot: string | null;
|
||||
agentName: string | null;
|
||||
adapterType: string | null;
|
||||
adapterType: AgentAdapterType | null;
|
||||
capabilities: string | null;
|
||||
agentDefaultsPayload: Record<string, unknown> | null;
|
||||
claimSecretExpiresAt: Date | null;
|
||||
claimSecretConsumedAt: Date | null;
|
||||
createdAgentId: string | null;
|
||||
approvedByUserId: string | null;
|
||||
approvedAt: Date | null;
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface Company {
|
||||
budgetMonthlyCents: number;
|
||||
spentMonthlyCents: number;
|
||||
requireBoardApprovalForNewAgents: boolean;
|
||||
brandColor: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
AGENT_ADAPTER_TYPES,
|
||||
INVITE_JOIN_TYPES,
|
||||
JOIN_REQUEST_STATUSES,
|
||||
JOIN_REQUEST_TYPES,
|
||||
@@ -17,7 +18,7 @@ export type CreateCompanyInvite = z.infer<typeof createCompanyInviteSchema>;
|
||||
export const acceptInviteSchema = z.object({
|
||||
requestType: z.enum(JOIN_REQUEST_TYPES),
|
||||
agentName: z.string().min(1).max(120).optional(),
|
||||
adapterType: z.string().min(1).max(120).optional(),
|
||||
adapterType: z.enum(AGENT_ADAPTER_TYPES).optional(),
|
||||
capabilities: z.string().max(4000).optional().nullable(),
|
||||
agentDefaultsPayload: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
});
|
||||
@@ -31,6 +32,12 @@ export const listJoinRequestsQuerySchema = z.object({
|
||||
|
||||
export type ListJoinRequestsQuery = z.infer<typeof listJoinRequestsQuerySchema>;
|
||||
|
||||
export const claimJoinRequestApiKeySchema = z.object({
|
||||
claimSecret: z.string().min(16).max(256),
|
||||
});
|
||||
|
||||
export type ClaimJoinRequestApiKey = z.infer<typeof claimJoinRequestApiKeySchema>;
|
||||
|
||||
export const updateMemberPermissionsSchema = z.object({
|
||||
grants: z.array(
|
||||
z.object({
|
||||
|
||||
@@ -15,6 +15,7 @@ export const updateCompanySchema = createCompanySchema
|
||||
status: z.enum(COMPANY_STATUSES).optional(),
|
||||
spentMonthlyCents: z.number().int().nonnegative().optional(),
|
||||
requireBoardApprovalForNewAgents: z.boolean().optional(),
|
||||
brandColor: z.string().regex(/^#[0-9a-fA-F]{6}$/).nullable().optional(),
|
||||
});
|
||||
|
||||
export type UpdateCompany = z.infer<typeof updateCompanySchema>;
|
||||
|
||||
@@ -102,11 +102,13 @@ export {
|
||||
createCompanyInviteSchema,
|
||||
acceptInviteSchema,
|
||||
listJoinRequestsQuerySchema,
|
||||
claimJoinRequestApiKeySchema,
|
||||
updateMemberPermissionsSchema,
|
||||
updateUserCompanyAccessSchema,
|
||||
type CreateCompanyInvite,
|
||||
type AcceptInvite,
|
||||
type ListJoinRequestsQuery,
|
||||
type ClaimJoinRequestApiKey,
|
||||
type UpdateMemberPermissions,
|
||||
type UpdateUserCompanyAccess,
|
||||
} from "./access.js";
|
||||
|
||||
Reference in New Issue
Block a user