-
Notifications
You must be signed in to change notification settings - Fork 762
[FEAT]: GUI: Adding Entra Auth Option to New Targets #1762
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7772d77
8d0c919
3d59086
51ec042
91e8912
e086c5a
3e0b62d
c7282df
17b25c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ import { | |
| Button, | ||
| Input, | ||
| Label, | ||
| Radio, | ||
| RadioGroup, | ||
| Select, | ||
| Switch, | ||
| Text, | ||
|
|
@@ -32,6 +34,26 @@ const TARGET_TYPE_CONFIG: Record<string, 'openai' | 'azureml'> = { | |
|
|
||
| const SUPPORTED_TARGET_TYPES = Object.keys(TARGET_TYPE_CONFIG) | ||
|
|
||
| type AuthMode = 'api_key' | 'entra' | ||
|
|
||
| // Mirrors backend's hostname-suffix check (list in target_service.py). | ||
| // The backend still does the check and will reject unsupported endpoints, but this allows us to show a warning in the UI if the user selects Microsoft Entra authentication with a non-Azure OpenAI endpoint. | ||
| const AZURE_OPENAI_HOSTNAME_SUFFIXES = [ | ||
| '.openai.azure.com', | ||
| '.ai.azure.com', | ||
| '.services.ai.azure.com', | ||
| '.cognitiveservices.azure.com', | ||
| ] | ||
|
|
||
| function isAzureOpenAiEndpoint(endpoint: string): boolean { | ||
| try { | ||
| const host = new URL(endpoint).hostname.toLowerCase() | ||
| return AZURE_OPENAI_HOSTNAME_SUFFIXES.some((s) => host.endsWith(s)) | ||
| } catch { | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| interface CreateTargetDialogProps { | ||
| open: boolean | ||
| onClose: () => void | ||
|
|
@@ -45,6 +67,7 @@ export default function CreateTargetDialog({ open, onClose, onCreated }: CreateT | |
| const [modelName, setModelName] = useState('') | ||
| const [hasDifferentUnderlying, setHasDifferentUnderlying] = useState(false) | ||
| const [underlyingModel, setUnderlyingModel] = useState('') | ||
| const [authMode, setAuthMode] = useState<AuthMode>('api_key') | ||
| const [apiKey, setApiKey] = useState('') | ||
| const [maxNewTokens, setMaxNewTokens] = useState('400') | ||
| const [temperature, setTemperature] = useState('1.0') | ||
|
|
@@ -54,14 +77,19 @@ export default function CreateTargetDialog({ open, onClose, onCreated }: CreateT | |
| const [error, setError] = useState<string | null>(null) | ||
| const [fieldErrors, setFieldErrors] = useState<{ targetType?: string; endpoint?: string }>({}) | ||
|
|
||
| const isAzureML = TARGET_TYPE_CONFIG[targetType] === 'azureml' | ||
| const targetKind = TARGET_TYPE_CONFIG[targetType] | ||
| const isAzureML = targetKind === 'azureml' | ||
| const isOpenAi = targetKind === 'openai' | ||
| const isEntra = authMode === 'entra' | ||
| const showNonAzureEntraWarning = isEntra && isOpenAi && endpoint !== '' && !isAzureOpenAiEndpoint(endpoint) | ||
|
|
||
| const resetForm = () => { | ||
| setTargetType('') | ||
| setEndpoint('') | ||
| setModelName('') | ||
| setHasDifferentUnderlying(false) | ||
| setUnderlyingModel('') | ||
| setAuthMode('api_key') | ||
| setApiKey('') | ||
| setMaxNewTokens('400') | ||
| setTemperature('1.0') | ||
|
|
@@ -94,7 +122,7 @@ export default function CreateTargetDialog({ open, onClose, onCreated }: CreateT | |
| endpoint, | ||
| } | ||
| if (modelName) params.model_name = modelName | ||
| if (apiKey) params.api_key = apiKey | ||
| if (!isEntra && apiKey) params.api_key = apiKey | ||
|
|
||
| if (hasDifferentUnderlying && underlyingModel) params.underlying_model = underlyingModel | ||
|
|
||
|
|
@@ -112,6 +140,7 @@ export default function CreateTargetDialog({ open, onClose, onCreated }: CreateT | |
| await targetsApi.createTarget({ | ||
| type: targetType, | ||
| params, | ||
| ...(isEntra ? { auth_mode: 'entra' as const } : {}), | ||
| }) | ||
| resetForm() | ||
| onCreated() | ||
|
|
@@ -243,15 +272,40 @@ export default function CreateTargetDialog({ open, onClose, onCreated }: CreateT | |
| </> | ||
| )} | ||
|
|
||
| <Field label="API Key"> | ||
| <Input | ||
| type="password" | ||
| placeholder="API key (stored in memory only)" | ||
| value={apiKey} | ||
| onChange={(_, data) => setApiKey(data.value)} | ||
| /> | ||
| <Field label="Authentication"> | ||
| <RadioGroup | ||
| value={authMode} | ||
| onChange={(_, data) => { | ||
| const next = data.value as AuthMode | ||
| setAuthMode(next) | ||
| if (next === 'entra') setApiKey('') | ||
| }} | ||
| > | ||
| <Radio value="api_key" label="API Key" /> | ||
| <Radio value="entra" label="Microsoft Entra Authentication" /> | ||
| </RadioGroup> | ||
| </Field> | ||
|
|
||
| {showNonAzureEntraWarning && ( | ||
| <MessageBar intent="warning" className={styles.warningMessage}> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two things on this warning block:
|
||
| <MessageBarBody className={styles.warningMessageBody}> | ||
| Error: Entra auth only works with Azure OpenAI / AI Foundry endpoints (for example, | ||
| *.openai.azure.com or *.ai.azure.com). | ||
| </MessageBarBody> | ||
| </MessageBar> | ||
| )} | ||
|
|
||
| {!isEntra && ( | ||
| <Field label="API Key"> | ||
| <Input | ||
| type="password" | ||
| placeholder="API key (stored in memory only)" | ||
| value={apiKey} | ||
| onChange={(_, data) => setApiKey(data.value)} | ||
| /> | ||
| </Field> | ||
| )} | ||
|
|
||
| <Label size="small" style={{ color: tokens.colorNeutralForeground3 }}> | ||
| Targets can also be auto-populated by adding an initializer (e.g. <code>airt</code>) to your{' '} | ||
| <code>~/.pyrit/.pyrit_conf</code> file, which reads endpoints from your <code>.env</code> and{' '} | ||
|
|
@@ -266,7 +320,11 @@ export default function CreateTargetDialog({ open, onClose, onCreated }: CreateT | |
| <Button appearance="secondary" onClick={handleClose} disabled={submitting}> | ||
| Cancel | ||
| </Button> | ||
| <Button appearance="primary" onClick={handleSubmit} disabled={submitting || !targetType || !endpoint}> | ||
| <Button | ||
| appearance="primary" | ||
| onClick={handleSubmit} | ||
| disabled={submitting || !targetType || !endpoint || showNonAzureEntraWarning} | ||
| > | ||
| {submitting ? 'Creating...' : 'Create Target'} | ||
| </Button> | ||
| </DialogActions> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ | |
| This module defines the Instance models for runtime target management. | ||
| """ | ||
|
|
||
| from typing import Any, Optional | ||
| from typing import Any, Literal, Optional | ||
|
|
||
| from pydantic import BaseModel, Field | ||
|
|
||
|
|
@@ -80,3 +80,11 @@ class CreateTargetRequest(BaseModel): | |
|
|
||
| type: str = Field(..., description="Target type (e.g., 'OpenAIChatTarget')") | ||
| params: dict[str, Any] = Field(default_factory=dict, description="Target constructor parameters") | ||
| auth_mode: Literal["api_key", "entra"] = Field( | ||
| "api_key", | ||
| description=( | ||
| "Authentication mode. 'api_key' uses the api_key in params (default)" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing separator between the concatenated strings — this renders as |
||
| "'entra' uses Microsoft Entra ID; requires an Azure endpoint and is " | ||
| "supported by OpenAI-family targets and AzureMLChatTarget." | ||
| ), | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Authentication field is rendered unconditionally — the Entra radio shows for every entry in
TARGET_TYPE_CONFIG, and even before any type is picked. Today that's fine because all 7 entries are Entra-capable, but the moment someone adds a non-Azure target (HuggingFace, Ollama, etc.) to the dropdown, picking Entra → Submit will hit the backend's"does not support Entra"raise and surface as a confusing 500.I'd make Entra capability an explicit per-type property rather than an implicit assumption:
Then hide the whole Authentication field (and fall back to the plain API Key input) when
!targetConfig?.supportsEntra. Also worth gating it ontargetType !== ''so we don't show auth controls before a type is selected.