Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
d641a32
feat(app): capture vendor login credentials in the connect flow
tofikwest Jul 15, 2026
2c766e7
feat(app): redesign browser-automation first-time setup screen
tofikwest Jul 15, 2026
b882b09
feat(app): use split-card layout for browser-automation setup screen
tofikwest Jul 15, 2026
e7d6356
feat(api): detect vendor login methods to power smart connect
tofikwest Jul 15, 2026
84da388
feat(app): smart connect flow (analyze -> sign in -> capture)
tofikwest Jul 15, 2026
bfe819b
feat(app): group automations by connection with per-connection status
tofikwest Jul 15, 2026
de0ffad
feat: capture arbitrary extra login fields (workspace, subdomain, etc.)
tofikwest Jul 15, 2026
cc92e10
fix(api): degrade login analysis to manual when Browserbase is unavai…
tofikwest Jul 15, 2026
b0500b9
chore(api): log full error/stack when login analysis falls back to ma…
tofikwest Jul 15, 2026
83bc850
fix(app): require a real login URL in connect flow; clearer 'no form …
tofikwest Jul 15, 2026
0aeb37a
feat(browserbase): run vendor login analysis as a background task
tofikwest Jul 15, 2026
15a3778
fix(app): accept any vendor URL in connect flow, not the exact login …
tofikwest Jul 15, 2026
0b35e71
fix(app): resume vendor connect flow after navigation; static https p…
tofikwest Jul 15, 2026
a3e7967
fix(app): render detected login methods as proper labels (SSO, not Sso)
tofikwest Jul 15, 2026
d452299
feat(browserbase): sign in for the user from a chooser instead of a r…
tofikwest Jul 15, 2026
f7fdd0a
fix(app): move "how it works" to the left on the browser automations …
tofikwest Jul 15, 2026
ee8ff41
fix(app): drop the redundant "detected" panel once the method chooser…
tofikwest Jul 15, 2026
a4c62ce
fix(app): declutter sign-in details form with progressive disclosure
tofikwest Jul 15, 2026
54351f0
fix(app): give the live sign-in browser more room; calmer message whe…
tofikwest Jul 15, 2026
83bc9ee
docs(api): clarify OP_SERVICE_ACCOUNT_TOKEN — what it enables and whe…
tofikwest Jul 15, 2026
27aafab
feat(browserbase): classify why an automated sign-in couldn't complete
tofikwest Jul 15, 2026
383c072
feat(browserbase): watch the automated sign-in and take over in place
tofikwest Jul 15, 2026
bbb09b2
fix(browserbase): fill each sign-in field in its own action
tofikwest Jul 15, 2026
eece6f2
fix(app): keep the browser open on any sign-in failure instead of a t…
tofikwest Jul 16, 2026
e524153
fix(app): don't act on a stale run result when starting a new sign-in
tofikwest Jul 16, 2026
c9d08e4
fix(app): guide 2FA take-over to the authenticator code (passkeys can…
tofikwest Jul 16, 2026
6d85e40
fix(app): keep the 2FA take-over hint vendor-neutral
tofikwest Jul 16, 2026
0fcdcf5
fix(app): warn up front when a vendor only offers non-unattended sign-in
tofikwest Jul 16, 2026
3d2cc9c
fix(app): drop the "what we detect" placeholder panel from the connec…
tofikwest Jul 16, 2026
832b6ce
feat(app): show who's in control of the live sign-in browser
tofikwest Jul 16, 2026
ec9a50b
feat(app): narrate the automated sign-in in real time
tofikwest Jul 16, 2026
621ddb8
feat(app): redesign the live sign-in as an activity panel (design 1b)
tofikwest Jul 16, 2026
4cdb6a7
feat(browserbase): manage connections — edit and remove (design 1f)
tofikwest Jul 16, 2026
32eb90f
fix(app): don't show a fresh evidence task as already connected
tofikwest Jul 16, 2026
e976f2f
fix(app): taller live browser; connected copy points to the next step
tofikwest Jul 16, 2026
327361a
feat(app): "Browser evidence" section — 1n header + 3-step empty state
tofikwest Jul 16, 2026
4333622
feat(app): restructure browser evidence active view into connection +…
tofikwest Jul 16, 2026
51708ab
feat(app): add run-history strip and run-detail overlay to browser ev…
tofikwest Jul 16, 2026
056a286
feat(api): live test-run for browser instructions before saving
tofikwest Jul 16, 2026
d4fc19f
feat(app): split composer — write, watch the AI test it live, then save
tofikwest Jul 16, 2026
660b013
feat(app): edit instruction cadence inline; remove old config dialog
tofikwest Jul 16, 2026
5aa4a0e
test(api): use a valid failure code in the instruction-test spec
tofikwest Jul 16, 2026
830b712
fix(api): capture the browser at 1920x1080 for a sharper live view
tofikwest Jul 16, 2026
9363db2
feat(app): gentle success beat when a connection signs in
tofikwest Jul 16, 2026
72866b0
feat(app): make the pass/fail check first-class with example chips
tofikwest Jul 16, 2026
b33a447
fix(app): bind new instructions to the just-connected vendor; align a…
tofikwest Jul 16, 2026
60ec0a7
fix(api): stop re-login from misreading a successful sign-in as failed
tofikwest Jul 16, 2026
84a4b2d
feat(app): enlarge the test-run live view to fill its panel
tofikwest Jul 16, 2026
431f409
fix(api): don't call a login page a "session expired"
tofikwest Jul 16, 2026
dd5d20c
feat(app): clean up the connection chip and frame the pass/fail check
tofikwest Jul 16, 2026
321ce5b
fix(api): give test runs more time and nudge the agent to be efficient
tofikwest Jul 16, 2026
e0a1b73
feat(app): align the first field with the live view and clarify copy
tofikwest Jul 16, 2026
4f2f13c
feat(api): make the navigation model swappable and let it self-correct
tofikwest Jul 16, 2026
336c766
feat(api): default navigation to OpenAI's computer-use model
tofikwest Jul 16, 2026
28e4450
feat(api): fall back to Claude at runtime if the navigation model fails
tofikwest Jul 16, 2026
b790b30
feat(api): default navigation to GPT-5.6 Terra
tofikwest Jul 16, 2026
d719dfd
fix(api): capture the named item and judge the check against that target
tofikwest Jul 16, 2026
9255728
fix(app): align the instruction field with the live view top
tofikwest Jul 16, 2026
befaea6
feat(app): drop the manual "start from a specific page" option
tofikwest Jul 16, 2026
5717f2a
feat: target the authenticated home so runs reuse the session
tofikwest Jul 16, 2026
f81d436
fix(api): capture the full page so evidence has no empty gap
tofikwest Jul 16, 2026
741569d
feat(app): one flow after connect, and support multiple vendors
tofikwest Jul 17, 2026
21b669c
fix(app): stop offering passkey as a sign-in method
tofikwest Jul 17, 2026
8f61e84
feat: drive SSO up to the identity provider automatically
tofikwest Jul 17, 2026
527d7a0
feat(app): smart one-click reconnect for expired connections
tofikwest Jul 17, 2026
d54c8e8
feat(api): render human-facing sign-in at a smaller viewport for legi…
tofikwest Jul 21, 2026
aa7f6bf
feat(app): animated activity border showing who's driving the live view
tofikwest Jul 21, 2026
780fa3a
fix(app): make the activity border visible and stop it lagging
tofikwest Jul 22, 2026
30f493b
fix(api): reduce vendor-page motion so the live view stops looking laggy
tofikwest Jul 22, 2026
759b335
fix(app): make the activity border render reliably via inline styles
tofikwest Jul 22, 2026
4316d9d
feat(app): show the activity border only while the AI is driving
tofikwest Jul 22, 2026
834355d
feat(app): implement the "breathing glow" AI control ring
tofikwest Jul 22, 2026
21891d8
fix(app): soften the AI control ring
tofikwest Jul 22, 2026
fbe749b
feat(app): add the "AI is controlling" pill to the control ring
tofikwest Jul 22, 2026
ceda7d5
feat(app): show a "Your turn" pill when the user is driving
tofikwest Jul 22, 2026
159768c
fix(app): drop the ring border and fix the sign-in method chooser
tofikwest Jul 22, 2026
e7eb4db
fix(app): fade the control ring in instead of popping it in
tofikwest Jul 22, 2026
66daf49
fix(app): confirm before discarding an in-progress live sign-in
tofikwest Jul 22, 2026
ec2e5fe
fix(app): show the control ring on browser load and hug the view
tofikwest Jul 22, 2026
447fe8c
fix(app): drop the control-ring glow, keep the status pill
tofikwest Jul 22, 2026
eec7be9
feat(app): add the Browser Connections settings page
tofikwest Jul 23, 2026
919e1d2
feat: count automations per browser connection
tofikwest Jul 23, 2026
2e8985e
feat(db): add browser automation steps schema
tofikwest Jul 23, 2026
f9800c0
feat(api): store browser automations as ordered steps
tofikwest Jul 23, 2026
eb5a2ef
feat(api): run browser automations step by step
tofikwest Jul 23, 2026
e545c5b
feat(app): carry automation steps through the app types and hooks
tofikwest Jul 23, 2026
eca49ee
feat(app): multi-step (multi-vendor) automation composer
tofikwest Jul 23, 2026
86b1bb0
fix(app): guard the composer against a missing connections prop
tofikwest Jul 23, 2026
421072c
fix(app): tidy the step card — hostname label, single-step chrome, sp…
tofikwest Jul 23, 2026
586c412
feat(db): add BrowserAutomationDraft schema
tofikwest Jul 23, 2026
e51b880
feat(api): browser automation draft endpoints
tofikwest Jul 23, 2026
ad2a5d8
feat(app): autosave + resume browser automation drafts
tofikwest Jul 23, 2026
0f9ff77
feat(app): automation-centric evidence list with vendor chains (desig…
tofikwest Jul 23, 2026
28709ea
fix(app): make the connect flow resume from anywhere on refresh
tofikwest Jul 23, 2026
feb3da9
fix(app): tighten the step-card type scale to fit the compact composer
tofikwest Jul 23, 2026
4451998
fix(app): shrink the step-card connection picker to match
tofikwest Jul 23, 2026
5968c73
feat: capture a focused close-up beside the full-page evidence (optio…
tofikwest Jul 23, 2026
c9390a8
fix(app): draft vendor identity, no duplicate evidence shot, capped p…
tofikwest Jul 23, 2026
baa39f7
fix(app): compact the automation row
tofikwest Jul 23, 2026
fc43069
feat(app): show real vendor logos across browser automations
tofikwest Jul 23, 2026
dc4b98e
fix(app): shrink the automation row's schedule and action controls
tofikwest Jul 23, 2026
ffe8910
feat(app): compact schedule menu in the automation row (designer opti…
tofikwest Jul 23, 2026
28d806d
feat(app): fold schedule into a Run split button; neutral action icons
tofikwest Jul 23, 2026
71b972a
feat(app): match designer's C mockup for the automation row
tofikwest Jul 23, 2026
11bee51
fix(app): neutral schedule half; clearer "first run" label
tofikwest Jul 23, 2026
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
24 changes: 22 additions & 2 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,28 @@ OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GROQ_API_KEY=

# 1Password service account token for browser-automation credential storage.
# Optional: when unset, browser automations fall back to human re-authentication.
# Browserbase — the cloud browser that powers Browser Automations.
# REQUIRED for the browser-automation feature (connect, analyze, runs).
BROWSERBASE_API_KEY=
BROWSERBASE_PROJECT_ID=
# Optional model overrides (defaults are sensible; no need to set these).
# BROWSERBASE_CUA_MODEL: screenshot-based navigation agent. Defaults to
# "openai/gpt-5.6-terra" (needs OPENAI_API_KEY; falls back to Claude if it's
# missing). Set to "openai/gpt-5.6-sol" for max accuracy, or an "anthropic/…"
# computer-use model to use Claude instead.
# BROWSERBASE_STAGEHAND_MODEL: model behind page reading / pass-fail verdicts —
# keep this a general model (Claude), NOT a computer-use model.
BROWSERBASE_CUA_MODEL=
BROWSERBASE_STAGEHAND_MODEL=

# 1Password service account token (starts with "ops_") for storing browser-
# automation logins in a Comp-managed vault.
# Enables the "Sign in for me" password path: Comp stores the login and signs in
# on its own (including generating 2FA codes) for the first sign-in and for
# scheduled runs.
# Optional — when unset, that path is unavailable and users sign in manually in
# the live browser each time (no unattended re-login).
# Create one at 1Password → Developer → Service Accounts (needs vault-create access).
OP_SERVICE_ACCOUNT_TOKEN=

# Inference.net Catalyst tracing (optional — no-op if CATALYST_OTLP_TOKEN is unset)
Expand Down
110 changes: 110 additions & 0 deletions apps/api/src/browserbase/browser-auth-profile.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jest.mock('@db', () => ({
findFirst: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
deleteMany: jest.fn(),
},
browserbaseContext: {
Expand All @@ -19,6 +20,9 @@ jest.mock('@db', () => ({
updateMany: jest.fn(),
deleteMany: jest.fn(),
},
browserAutomation: {
findMany: jest.fn(),
},
},
}));

Expand Down Expand Up @@ -188,4 +192,110 @@ describe('BrowserAuthProfileService', () => {
where: { organizationId: 'org_1', contextId: '__PENDING__' },
});
});

describe('listProfiles', () => {
it('attaches an automation count per connection by hostname', async () => {
(db.browserAuthProfile.findMany as jest.Mock).mockResolvedValue([
{ id: 'bap_gh', organizationId: 'org_1', hostname: 'github.com' },
{ id: 'bap_aws', organizationId: 'org_1', hostname: 'aws.amazon.com' },
{ id: 'bap_dd', organizationId: 'org_1', hostname: 'datadoghq.com' },
]);
(db.browserAutomation.findMany as jest.Mock).mockResolvedValue([
{ targetUrl: 'https://github.com/acme/repo' },
{ targetUrl: 'https://github.com/acme/other' },
{ targetUrl: 'https://aws.amazon.com/console' },
{ targetUrl: 'not-a-url' }, // malformed -> skipped, not fatal
]);

const result = await service.listProfiles('org_1');

expect(db.browserAutomation.findMany).toHaveBeenCalledWith({
where: { task: { organizationId: 'org_1' } },
select: { targetUrl: true },
});
expect(result.find((p) => p.id === 'bap_gh')?.automationCount).toBe(2);
expect(result.find((p) => p.id === 'bap_aws')?.automationCount).toBe(1);
expect(result.find((p) => p.id === 'bap_dd')?.automationCount).toBe(0);
});
});

describe('updateProfile / deleteProfile', () => {
const existing = {
id: 'bap_1',
organizationId: 'org_1',
hostname: 'app.example.com',
displayName: 'Example',
status: 'verified',
};

beforeEach(() => {
(db.browserAuthProfile.update as jest.Mock).mockImplementation(
({ data }) => ({ ...existing, ...data }),
);
(db.browserAuthProfile.delete as jest.Mock).mockResolvedValue(existing);
});

it('updates only the display name (trimmed)', async () => {
(db.browserAuthProfile.findFirst as jest.Mock).mockResolvedValue(existing);
await service.updateProfile({
organizationId: 'org_1',
profileId: 'bap_1',
displayName: ' New name ',
});
expect(db.browserAuthProfile.update).toHaveBeenCalledWith({
where: { id: 'bap_1' },
data: { displayName: 'New name' },
});
});

it('keeps the connection signed in when the URL stays on the same host', async () => {
(db.browserAuthProfile.findFirst as jest.Mock).mockResolvedValue(existing);
await service.updateProfile({
organizationId: 'org_1',
profileId: 'bap_1',
url: 'https://app.example.com/account/login',
});
const data = (db.browserAuthProfile.update as jest.Mock).mock.calls[0][0]
.data;
expect(data.lastAuthCheckUrl).toBe(
'https://app.example.com/account/login',
);
expect(data.status).toBeUndefined();
});

it('marks needs_reauth when the URL moves to a different host', async () => {
(db.browserAuthProfile.findFirst as jest.Mock).mockResolvedValue(existing);
await service.updateProfile({
organizationId: 'org_1',
profileId: 'bap_1',
url: 'https://other.com/login',
});
const data = (db.browserAuthProfile.update as jest.Mock).mock.calls[0][0]
.data;
expect(data.status).toBe('needs_reauth');
});

it('deletes the profile', async () => {
(db.browserAuthProfile.findFirst as jest.Mock).mockResolvedValue(existing);
const result = await service.deleteProfile({
organizationId: 'org_1',
profileId: 'bap_1',
});
expect(db.browserAuthProfile.delete).toHaveBeenCalledWith({
where: { id: 'bap_1' },
});
expect(result.success).toBe(true);
});

it('throws when updating a missing profile', async () => {
(db.browserAuthProfile.findFirst as jest.Mock).mockResolvedValue(null);
await expect(
service.updateProfile({
organizationId: 'org_1',
profileId: 'nope',
displayName: 'x',
}),
).rejects.toThrow('not found');
});
});
});
91 changes: 84 additions & 7 deletions apps/api/src/browserbase/browser-auth-profile.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import { db } from '@db';
import { db, Prisma } from '@db';
import {
defaultProfileDisplayName,
normalizeHostnameFromUrl,
normalizeLoginIdentity,
} from './browserbase-url';
import { BrowserbaseSessionService } from './browserbase-session.service';
import {
BrowserbaseSessionService,
INTERACTIVE_VIEWPORT,
} from './browserbase-session.service';
import { BrowserAuthProfileContextService } from './browser-auth-profile-context.service';
import {
BrowserbaseOrgContextService,
Expand Down Expand Up @@ -41,10 +44,33 @@ export class BrowserAuthProfileService {
) {}

async listProfiles(organizationId: string) {
return db.browserAuthProfile.findMany({
where: { organizationId },
orderBy: [{ hostname: 'asc' }, { updatedAt: 'desc' }],
});
const [profiles, automations] = await Promise.all([
db.browserAuthProfile.findMany({
where: { organizationId },
orderBy: [{ hostname: 'asc' }, { updatedAt: 'desc' }],
}),
db.browserAutomation.findMany({
where: { task: { organizationId } },
select: { targetUrl: true },
}),
]);

// Automations bind to a connection by hostname (not a FK), so tally per host.
const countByHost = new Map<string, number>();
for (const { targetUrl } of automations) {
let host: string;
try {
host = normalizeHostnameFromUrl(targetUrl);
} catch {
continue; // skip malformed targetUrls rather than fail the whole list
}
countByHost.set(host, (countByHost.get(host) ?? 0) + 1);
}

return profiles.map((profile) => ({
...profile,
automationCount: countByHost.get(profile.hostname) ?? 0,
}));
}

async getProfile({
Expand Down Expand Up @@ -163,7 +189,12 @@ export class BrowserAuthProfileService {
throw new NotFoundException('Browser auth profile not found');
}
const readyProfile = await this.profileContexts.ready(profile);
return this.sessions.createSessionWithContext(readyProfile.contextId);
// Human-facing session — use the smaller viewport so the sign-in page reads
// larger in the live view.
return this.sessions.createSessionWithContext(
readyProfile.contextId,
INTERACTIVE_VIEWPORT,
);
}

async verifyProfileSession(input: {
Expand Down Expand Up @@ -224,6 +255,52 @@ export class BrowserAuthProfileService {
});
}

async updateProfile(input: {
organizationId: string;
profileId: string;
displayName?: string;
url?: string;
}) {
const profile = await this.getProfile(input);
if (!profile) {
throw new NotFoundException('Browser auth profile not found');
}

const data: Prisma.BrowserAuthProfileUpdateInput = {};

const name = input.displayName?.trim();
if (name) data.displayName = name;

if (input.url !== undefined) {
data.lastAuthCheckUrl = input.url;
// A different hostname means the saved session no longer applies — the
// connection must be re-established. (Hostname is the connection identity,
// so we don't reassign it here.)
try {
if (normalizeHostnameFromUrl(input.url) !== profile.hostname) {
data.status = 'needs_reauth';
data.blockedReason = 'Sign-in URL changed — reconnect required.';
}
} catch {
// Ignore an unparseable URL — leave status untouched.
}
}

return db.browserAuthProfile.update({
where: { id: profile.id },
data,
});
}

async deleteProfile(input: { organizationId: string; profileId: string }) {
const profile = await this.getProfile(input);
if (!profile) {
throw new NotFoundException('Browser auth profile not found');
}
await db.browserAuthProfile.delete({ where: { id: profile.id } });
return { success: true, profile };
}

async markNeedsReauth(input: {
organizationId: string;
profileId: string;
Expand Down
81 changes: 80 additions & 1 deletion apps/api/src/browserbase/browser-auth-profiles.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
UseGuards,
} from '@nestjs/common';
import {
ApiBody,
ApiOperation,
Expand All @@ -18,7 +27,10 @@ import {
ResolveAuthProfileDto,
ResolveAuthProfileResponseDto,
SessionResponseDto,
SignInAuthProfileDto,
SignInAuthProfileResponseDto,
StoreAuthProfileCredentialsDto,
UpdateAuthProfileDto,
VerifyAuthProfileResponseDto,
VerifyAuthProfileSessionDto,
} from './dto/browserbase.dto';
Expand Down Expand Up @@ -134,9 +146,76 @@ export class BrowserAuthProfilesController {
username: dto.username,
password: dto.password,
totpSeed: dto.totpSeed,
extraFields: dto.extraFields,
})) as BrowserAuthProfileResponseDto;
}

@Post('profiles/:profileId/sign-in')
@RequirePermission('integration', 'update')
@ApiOperation({
summary: 'Automated sign-in for a browser auth profile',
description:
'Starts a background run that signs in to the vendor using the credentials stored for this profile, so the user does not have to type them into the browser. Returns a run handle to subscribe to; if the automated sign-in cannot complete (CAPTCHA, email/SMS code, SSO), the connect flow falls back to a live browser.',
})
@ApiParam({ name: 'profileId', description: 'Browser auth profile ID' })
@ApiBody({ type: SignInAuthProfileDto })
@ApiResponse({ status: 201, type: SignInAuthProfileResponseDto })
async signInProfile(
@OrganizationId() organizationId: string,
@Param('profileId') profileId: string,
@Body() dto: SignInAuthProfileDto,
): Promise<SignInAuthProfileResponseDto> {
return this.browserbaseService.signInAuthProfile({
organizationId,
profileId,
url: dto.url,
mode: dto.mode,
});
}

@Patch('profiles/:profileId')
@RequirePermission('integration', 'update')
@ApiOperation({
summary: 'Update a browser auth profile',
description:
'Edit a connection’s display name and/or sign-in URL. Changing to a different hostname marks the connection as needing reconnection.',
})
@ApiParam({ name: 'profileId', description: 'Browser auth profile ID' })
@ApiBody({ type: UpdateAuthProfileDto })
@ApiResponse({ status: 200, type: BrowserAuthProfileResponseDto })
async updateProfile(
@OrganizationId() organizationId: string,
@Param('profileId') profileId: string,
@Body() dto: UpdateAuthProfileDto,
): Promise<BrowserAuthProfileResponseDto> {
return (await this.browserbaseService.updateAuthProfile({
organizationId,
profileId,
displayName: dto.displayName,
url: dto.url,
})) as BrowserAuthProfileResponseDto;
}

@Delete('profiles/:profileId')
@RequirePermission('integration', 'delete')
@ApiOperation({
summary: 'Remove a browser auth profile',
description:
'Delete a connection and best-effort remove its stored login from the vault. Automations that relied on it stop running until reconnected.',
})
@ApiParam({ name: 'profileId', description: 'Browser auth profile ID' })
@ApiResponse({ status: 200 })
async deleteProfile(
@OrganizationId() organizationId: string,
@Param('profileId') profileId: string,
): Promise<{ success: boolean }> {
const result = await this.browserbaseService.deleteAuthProfile({
organizationId,
profileId,
});
return { success: result.success };
}

@Post('profiles/:profileId/needs-reauth')
@RequirePermission('integration', 'update')
@ApiOperation({
Expand Down
Loading
Loading