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
20 changes: 20 additions & 0 deletions apps/docs/providers/source-control/azure-devops.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ Service hook webhook secrets are generated automatically when Roomote
configures service hooks. Advanced values remain editable later in Settings →
Source Control.

#### Required API permissions

Under the app registration's **API permissions**, add the Azure DevOps
permissions **Code**, **Graph**, and **User Delegation / Impersonation**, save
them, and grant admin consent for the tenant.

Roomote requests the `.default` scope, so Microsoft Entra issues a token
covering whatever the app registration was already consented for. An app
registration missing these permissions still authenticates, and Azure DevOps
then rejects every API call with `401 Unauthorized`. Permissions added in the
Azure portal but not saved behave the same way, so re-check that the change was
saved before retrying a sync.

### Connect with your Microsoft account

Choose **Connect with your Microsoft account** in setup or Settings to authorize the Azure
Expand All @@ -69,6 +82,9 @@ linked account reference with the deployment and refreshes its short-lived
Entra access token as needed. The Entra client ID, client secret, and tenant ID
are still required so Roomote can refresh the connection in background jobs.

This mode uses the same app registration, so it needs the same
[API permissions](#required-api-permissions) as the service principal.

The linked account can be reconnected or replaced later in Settings. Switching
to PAT or service-principal mode removes the inactive credential path from the
deployment configuration.
Expand All @@ -85,6 +101,10 @@ https://dev.azure.com/<organization>/_apis/git/repositories?api-version=7.1

Roomote stores the results as Azure DevOps repository rows.

If the sync reports that Azure DevOps rejected the credential, the usual cause
in either Entra mode is a missing or unsaved
[API permission](#required-api-permissions) on the app registration.

Azure DevOps-backed tasks clone from the synced repository row, so sync must
run before launching an Azure DevOps-backed task. Worker tasks write
host- and clone-path-scoped Azure DevOps credentials into the file-backed Git
Expand Down
17 changes: 17 additions & 0 deletions apps/web/src/app/(onboarding)/setup/AdoSourceControlConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Blocks, Cog, PlugZap, UserCog, UserKey } from 'lucide-react';
import { ADO_ENTRA_REQUIRED_API_PERMISSIONS_TEXT } from '@roomote/types';

import { InstructionUrl } from './ProviderSetupInstructions';

type AdoAuthMode = 'pat' | 'entra' | 'delegated';
Expand Down Expand Up @@ -114,6 +116,21 @@ export function AdoSourceControlInstructions({
→ New registration → Create an app in the Microsoft tenant that can
access your Azure DevOps organization.
</p>
<p className="text-sm">
Open the app&apos;s{' '}
<span className="font-semibold text-foreground">API permissions</span> →
Add a permission → Azure DevOps, and grant{' '}
<span className="font-semibold text-foreground">
{ADO_ENTRA_REQUIRED_API_PERMISSIONS_TEXT}
</span>
. Then{' '}
<span className="font-semibold text-foreground">
save the permissions
</span>{' '}
and grant admin consent for the tenant. Azure does not apply permissions
until they are saved, and a missing one only shows up later as a 401
when Roomote syncs repositories.
</p>
{authMode === 'delegated' && (
<>
<p className="text-sm">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,13 +303,19 @@ export function StepSourceControlConnect({
adoAuthMode === 'delegated' &&
adoLinkedAccount.data?.account
) {
await saveAdoLinkedAccount.mutateAsync({
provider: 'ado',
values: {
ADO_AUTH_MODE: 'delegated',
ADO_LINKED_ACCOUNT_ID: adoLinkedAccount.data.account.accountId,
},
});
try {
await saveAdoLinkedAccount.mutateAsync({
provider: 'ado',
values: {
ADO_AUTH_MODE: 'delegated',
ADO_LINKED_ACCOUNT_ID: adoLinkedAccount.data.account.accountId,
},
});
} catch {
// The save verifies the connection against Azure DevOps, so a failure
// here (already surfaced by onError) means the sync would fail too.
return;
}
}

syncRepositories.mutate();
Expand Down
22 changes: 21 additions & 1 deletion apps/web/src/components/settings/SourceControlConfigForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import { useEffect, useMemo, useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import type { SetupSourceControlStatus } from '@roomote/types';
import {
ADO_ENTRA_REQUIRED_API_PERMISSIONS_TEXT,
type SetupSourceControlStatus,
} from '@roomote/types';

import { useTRPC } from '@/trpc/client';
import { Button, Check, Input, Spinner } from '@/components/system';
Expand Down Expand Up @@ -274,6 +277,23 @@ export function SourceControlConfigForm({
</button>
</div>
) : null}
{isAdo && adoAuthMode !== 'pat' ? (
<p className="max-w-xl text-sm text-muted-foreground">
The Microsoft Entra app registration needs the{' '}
<span className="font-medium text-foreground">
{ADO_ENTRA_REQUIRED_API_PERMISSIONS_TEXT}
</span>{' '}
API permissions, saved and admin-consented.{' '}
<a
href="https://docs.roomote.dev/providers/source-control/azure-devops#required-api-permissions"
target="_blank"
rel="noopener noreferrer"
className="font-medium text-foreground underline"
>
Setup guide
</a>
</p>
) : null}
{isAdo && adoAuthMode === 'delegated' ? (
<div className="max-w-xl rounded-md border p-3 text-sm">
<p className="text-muted-foreground">
Expand Down
142 changes: 142 additions & 0 deletions apps/web/src/trpc/commands/source-control/index.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading