mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 00:01:34 +00:00
oauth: add ServiceNow + make per-instance OAuth providers registry-driven (#9449)
* oauth: add ServiceNow provider; make per-instance OAuth registry-driven
ServiceNow's OAuth endpoints are per-instance
(https://<instance>.service-now.com/oauth_auth.do + /oauth_token.do), like
Snowflake's. Rather than add another bespoke special-case, generalize:
a registry entry may carry a `connect_config_template` (label/placeholder/
help_url + {instance}-templated auth_url/token_url + req_body_auth +
optional extra_params_key/strip_suffix). The instance-settings UI renders
one generic instance-name input for any such provider and substitutes
{instance} to build the per-client connect_config — a new per-instance
provider needs only a JSON entry, no frontend code.
- oauth_connect.json: servicenow + snowflake_oauth now carry a
connect_config_template (snowflake keeps its account_identifier
extra_params key for backward compatibility).
- windmill-oauth: add the ConnectConfigTemplate struct (frontend-only
metadata; the backend's existing connect_config override resolves the
concrete URLs generically — no other backend change).
- AuthSettings/InstanceSettings: replace the Snowflake + ServiceNow
special-cases with one registry-driven path (instanceInputs map,
setupTemplatedOauthUrls, loadInstanceInputs); per-instance providers are
derived from the registry for the builtins list + dropdown.
Pairs with windmill-integrations#139 (ServiceNow hub integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: point ee-repo-ref at servicenow-oauth EE branch (revert at merge)
Temporary CI pointer so check_ee_full / cargo_test build against the EE
slack-literal fix (windmill-ee-private#602). Revert to a pinned SHA once
that EE PR is merged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
3742e0659c5e97aab03b9efeea14cd94a3ac658a
|
||||
servicenow-oauth
|
||||
|
||||
+32
-16
@@ -154,29 +154,34 @@
|
||||
"zoho": {
|
||||
"auth_url": "https://accounts.zoho.com/oauth/v2/auth",
|
||||
"token_url": "https://accounts.zoho.com/oauth/v2/token",
|
||||
"scopes": [
|
||||
"ZohoAssist.sessionapi.ALL"
|
||||
],
|
||||
"scopes": ["ZohoAssist.sessionapi.ALL"],
|
||||
"extra_params": {
|
||||
"access_type": "offline"
|
||||
}
|
||||
},
|
||||
"snowflake_oauth": {},
|
||||
"snowflake_oauth": {
|
||||
"connect_config_template": {
|
||||
"display_name": "Snowflake",
|
||||
"label": "Snowflake Account Identifier",
|
||||
"placeholder": "<orgname>-<account_name>",
|
||||
"help_url": "https://docs.snowflake.com/en/user-guide/admin-account-identifier#using-an-account-name-as-an-identifier",
|
||||
"auth_url": "https://{instance}.snowflakecomputing.com/oauth/authorize",
|
||||
"token_url": "https://{instance}.snowflakecomputing.com/oauth/token-request",
|
||||
"req_body_auth": false,
|
||||
"extra_params_key": "account_identifier",
|
||||
"resource_mapping": { "account_identifier": "{instance}" }
|
||||
}
|
||||
},
|
||||
"apify": {
|
||||
"auth_url": "https://console.apify.com/authorize/oauth",
|
||||
"token_url": "https://console-backend.apify.com/oauth/apps/token",
|
||||
"scopes": [
|
||||
"profile",
|
||||
"full_api_access"
|
||||
],
|
||||
"scopes": ["profile", "full_api_access"],
|
||||
"extra_params": {}
|
||||
},
|
||||
"docusign": {
|
||||
"auth_url": "https://account.docusign.com/oauth/auth",
|
||||
"token_url": "https://account.docusign.com/oauth/token",
|
||||
"scopes": [
|
||||
"signature"
|
||||
],
|
||||
"scopes": ["signature"],
|
||||
"sandbox": {
|
||||
"auth_url": "https://account-d.docusign.com/oauth/auth",
|
||||
"token_url": "https://account-d.docusign.com/oauth/token"
|
||||
@@ -185,14 +190,25 @@
|
||||
"salesforce": {
|
||||
"auth_url": "https://login.salesforce.com/services/oauth2/authorize",
|
||||
"token_url": "https://login.salesforce.com/services/oauth2/token",
|
||||
"scopes": [
|
||||
"api",
|
||||
"refresh_token",
|
||||
"offline_access"
|
||||
],
|
||||
"scopes": ["api", "refresh_token", "offline_access"],
|
||||
"sandbox": {
|
||||
"auth_url": "https://test.salesforce.com/services/oauth2/authorize",
|
||||
"token_url": "https://test.salesforce.com/services/oauth2/token"
|
||||
}
|
||||
},
|
||||
"servicenow": {
|
||||
"connect_config_template": {
|
||||
"display_name": "ServiceNow",
|
||||
"label": "ServiceNow Instance",
|
||||
"placeholder": "<instance> (e.g. dev12345)",
|
||||
"help_url": "https://www.servicenow.com/docs/bundle/zurich-platform-security/page/administer/security/concept/c_OAuthApplications.html",
|
||||
"auth_url": "https://{instance}.service-now.com/oauth_auth.do",
|
||||
"token_url": "https://{instance}.service-now.com/oauth_token.do",
|
||||
"req_body_auth": true,
|
||||
"strip_suffix": ".service-now.com",
|
||||
"resource_mapping": {
|
||||
"instance_url": "https://{instance}.service-now.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,12 @@ pub struct OAuthConfig {
|
||||
/// entry, `build_oauth_clients` registers a second client under that key.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub sandbox: Option<OAuthSandboxOverride>,
|
||||
/// Frontend-only metadata for per-instance OAuth providers (Snowflake,
|
||||
/// ServiceNow, …) whose authorize/token URLs are derived from an
|
||||
/// admin-entered instance name. Ignored by the backend, which only ever
|
||||
/// sees the resulting concrete `connect_config`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub connect_config_template: Option<ConnectConfigTemplate>,
|
||||
}
|
||||
|
||||
/// URL overrides for an OAuth provider's sandbox environment. Inherits
|
||||
@@ -106,6 +112,43 @@ pub struct OAuthSandboxOverride {
|
||||
pub userinfo_url: Option<String>,
|
||||
}
|
||||
|
||||
/// Frontend metadata for a per-instance OAuth provider. The instance-settings
|
||||
/// UI renders one generic instance-name input and substitutes `{instance}` into
|
||||
/// `auth_url`/`token_url` to build the per-client `connect_config`. Adding a new
|
||||
/// per-instance provider needs only a registry entry carrying this template —
|
||||
/// no frontend code change. The backend never reads it.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ConnectConfigTemplate {
|
||||
/// Properly-cased provider name for the settings dropdown (e.g. "ServiceNow");
|
||||
/// the UI falls back to a capitalized registry key when absent.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub display_name: Option<String>,
|
||||
pub label: String,
|
||||
pub placeholder: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub help_url: Option<String>,
|
||||
pub auth_url: String,
|
||||
pub token_url: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub req_body_auth: Option<bool>,
|
||||
/// Key under `connect_config.extra_params` where the instance name is
|
||||
/// stored (defaults to `instance`). Snowflake uses `account_identifier` for
|
||||
/// backward compatibility with previously-saved configs.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extra_params_key: Option<String>,
|
||||
/// Optional host suffix stripped from the input before substitution (e.g.
|
||||
/// `.service-now.com`), so the admin can paste a full host or a bare name.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub strip_suffix: Option<String>,
|
||||
/// Maps OAuth-connected resource arg fields to value templates substituting
|
||||
/// `{instance}` (e.g. ServiceNow's `instance_url` ->
|
||||
/// `https://{instance}.service-now.com`). Applied by the resource-connect
|
||||
/// flow so the created resource carries the instance-specific fields the
|
||||
/// scripts need (ServiceNow's token response omits the host).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resource_mapping: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl OAuthConfig {
|
||||
/// Returns a copy of this config with sandbox URL overrides applied and
|
||||
/// the nested `sandbox` field cleared. Returns `None` if no overrides are
|
||||
@@ -817,6 +860,7 @@ mod tests {
|
||||
token_url: Some("https://account-d.example.com/oauth/token".to_string()),
|
||||
userinfo_url: None,
|
||||
}),
|
||||
connect_config_template: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
type ResourceType
|
||||
} from '$lib/gen'
|
||||
import { emptyString, truncateRev, urlize } from '$lib/utils'
|
||||
import oauthConnectRegistry from '$oauth_connect_registry'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import Path from './Path.svelte'
|
||||
import { Button, Skeleton } from './common'
|
||||
@@ -489,12 +490,25 @@
|
||||
throw Error(`Resource at path ${path} already exists. Delete it or pick another path`)
|
||||
}
|
||||
|
||||
if (resourceType == 'snowflake_oauth') {
|
||||
const account_identifier = extra_params.find(([key, _]) => key == 'account_identifier')
|
||||
if (account_identifier) {
|
||||
args['account_identifier'] = account_identifier[1]
|
||||
// Per-instance OAuth providers (Snowflake, ServiceNow, …): copy the
|
||||
// admin-configured instance from the OAuth client's extra_params into the
|
||||
// resource args, per the registry template's resource_mapping (e.g.
|
||||
// ServiceNow -> instance_url: https://{instance}.service-now.com). Generic
|
||||
// so a new per-instance provider needs only a registry entry.
|
||||
const connectTemplate = (oauthConnectRegistry as Record<string, any>)[resourceType]
|
||||
?.connect_config_template
|
||||
if (connectTemplate?.resource_mapping) {
|
||||
const instanceKey = connectTemplate.extra_params_key ?? 'instance'
|
||||
const found = extra_params.find(([key, _]) => key === instanceKey)
|
||||
if (found) {
|
||||
for (const [argField, valueTemplate] of Object.entries(
|
||||
connectTemplate.resource_mapping as Record<string, string>
|
||||
)) {
|
||||
args[argField] = valueTemplate.replaceAll('{instance}', found[1])
|
||||
}
|
||||
}
|
||||
} else if (resourceType === 'quickbooks' && responseExtra['realmId']) {
|
||||
}
|
||||
if (resourceType === 'quickbooks' && responseExtra['realmId']) {
|
||||
args['realmId'] = responseExtra['realmId']
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,10 @@
|
||||
import oauthConnectRegistry from '$oauth_connect_registry'
|
||||
|
||||
interface Props {
|
||||
snowflakeAccountIdentifier?: string
|
||||
// Per-instance OAuth providers (Snowflake, ServiceNow, …): instance name
|
||||
// keyed by provider, used to build their per-instance connect_config URLs.
|
||||
// Required (and always bound by InstanceSettings) so it is never undefined.
|
||||
instanceInputs: Record<string, string>
|
||||
oauths?: Record<string, any>
|
||||
requirePreexistingUserForOauth?: boolean
|
||||
baseUrl?: string
|
||||
@@ -39,7 +42,7 @@
|
||||
}
|
||||
|
||||
let {
|
||||
snowflakeAccountIdentifier = $bindable(),
|
||||
instanceInputs = $bindable(),
|
||||
oauths = $bindable(),
|
||||
requirePreexistingUserForOauth = $bindable(),
|
||||
baseUrl,
|
||||
@@ -49,9 +52,6 @@
|
||||
}: Props = $props()
|
||||
|
||||
$effect(() => {
|
||||
if (snowflakeAccountIdentifier == undefined) {
|
||||
snowflakeAccountIdentifier = ''
|
||||
}
|
||||
if (oauths == undefined) {
|
||||
oauths = {}
|
||||
}
|
||||
@@ -79,7 +79,6 @@
|
||||
'visma',
|
||||
'sage_intacct',
|
||||
'spotify',
|
||||
'snowflake_oauth',
|
||||
'teams',
|
||||
'zoho',
|
||||
'xero',
|
||||
@@ -96,9 +95,20 @@
|
||||
const windmillBuiltinsWithSandbox = Object.entries(oauthConnectRegistry)
|
||||
.filter(([, cfg]) => cfg && typeof cfg === 'object' && 'sandbox' in cfg)
|
||||
.map(([name]) => name)
|
||||
// Per-instance providers (Snowflake, ServiceNow, …): registry entries that
|
||||
// carry a `connect_config_template`. Derived from the registry so adding a
|
||||
// new one needs only a JSON entry — they get a builtin tile + the generic
|
||||
// instance-name input below, with no frontend change.
|
||||
const connectConfigTemplates: Record<string, any> = Object.fromEntries(
|
||||
Object.entries(oauthConnectRegistry)
|
||||
.filter(([, cfg]) => cfg && typeof cfg === 'object' && 'connect_config_template' in cfg)
|
||||
.map(([name, cfg]) => [name, (cfg as any).connect_config_template])
|
||||
)
|
||||
const windmillBuiltinsTemplated = Object.keys(connectConfigTemplates)
|
||||
const windmillBuiltins = [
|
||||
...windmillBuiltinsBase,
|
||||
...windmillBuiltinsWithSandbox.map((n) => `${n}_sandbox`)
|
||||
...windmillBuiltinsWithSandbox.map((n) => `${n}_sandbox`),
|
||||
...windmillBuiltinsTemplated
|
||||
]
|
||||
|
||||
let showCustomOAuthForm = $state(false)
|
||||
@@ -238,6 +248,20 @@
|
||||
}
|
||||
})
|
||||
|
||||
// Add per-instance providers (registry entries with a connect_config_template)
|
||||
windmillBuiltinsTemplated.forEach((name) => {
|
||||
if (!oauths || !oauths[name]) {
|
||||
const icon = getOAuthProviderIcon(name)
|
||||
items.push({
|
||||
// Prefer the template's display_name (properly cased, e.g. "ServiceNow")
|
||||
// over capitalize(name) which yields "Servicenow"/"Snowflake_oauth".
|
||||
displayName: connectConfigTemplates[name]?.display_name ?? capitalize(name),
|
||||
action: () => createOAuthClient(name),
|
||||
icon: icon
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Add custom option
|
||||
items.push({
|
||||
displayName: `Custom OAuth client ${!$enterpriseLicense ? '(requires ee)' : ''}`,
|
||||
@@ -486,19 +510,23 @@
|
||||
{:else if !windmillBuiltins.includes(k) && k != 'slack'}
|
||||
<CustomOauth bind:connect_config={oauths[k]['connect_config']} />
|
||||
{/if}
|
||||
{#if k == 'snowflake_oauth'}
|
||||
{#if connectConfigTemplates[k]}
|
||||
{@const tmpl = connectConfigTemplates[k]}
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-xs flex gap-2 items-center"
|
||||
><a
|
||||
href="https://docs.snowflake.com/en/user-guide/admin-account-identifier#using-an-account-name-as-an-identifier"
|
||||
target="_blank">Snowflake Account Identifier</a
|
||||
><ExternalLink size={12} /></span
|
||||
>
|
||||
<span class="text-primary font-semibold text-xs flex gap-2 items-center">
|
||||
{#if tmpl.help_url}
|
||||
<a href={tmpl.help_url} target="_blank">{tmpl.label}</a><ExternalLink
|
||||
size={12}
|
||||
/>
|
||||
{:else}
|
||||
{tmpl.label}
|
||||
{/if}
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="<orgname>-<account_name>"
|
||||
placeholder={tmpl.placeholder}
|
||||
required={true}
|
||||
bind:value={snowflakeAccountIdentifier}
|
||||
bind:value={instanceInputs[k]}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { setLicense } from '$lib/enterpriseUtils'
|
||||
import AuthSettings from './AuthSettings.svelte'
|
||||
import oauthConnectRegistry from '$oauth_connect_registry'
|
||||
import InstanceSetting from './InstanceSetting.svelte'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import { ExternalLink, Loader2 } from 'lucide-svelte'
|
||||
@@ -54,7 +55,9 @@
|
||||
|
||||
let initialValues: Record<string, any> = $state({})
|
||||
let baseUrlIsFallback = $state(false)
|
||||
let snowflakeAccountIdentifier = $state('')
|
||||
// Per-instance OAuth providers (Snowflake, ServiceNow, …): instance name
|
||||
// keyed by provider, used to build their per-instance connect_config URLs.
|
||||
let instanceInputs: Record<string, string> = $state({})
|
||||
let version: string = $state('')
|
||||
let loading = $state(true)
|
||||
|
||||
@@ -147,12 +150,8 @@
|
||||
$values = nvalues
|
||||
loading = false
|
||||
|
||||
// populate snowflake account identifier from db
|
||||
const account_identifier =
|
||||
oauths?.snowflake_oauth?.connect_config?.extra_params?.account_identifier
|
||||
if (account_identifier) {
|
||||
snowflakeAccountIdentifier = account_identifier
|
||||
}
|
||||
// populate per-instance OAuth provider inputs (snowflake, servicenow, …) from db
|
||||
loadInstanceInputs(oauths)
|
||||
}
|
||||
|
||||
export async function saveSettings() {
|
||||
@@ -162,13 +161,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
oauths?.snowflake_oauth &&
|
||||
oauths?.snowflake_oauth?.connect_config?.extra_params?.account_identifier !==
|
||||
snowflakeAccountIdentifier
|
||||
) {
|
||||
setupSnowflakeUrls()
|
||||
}
|
||||
setupTemplatedOauthUrls()
|
||||
|
||||
// Remove empty or invalid entries for critical error channels
|
||||
$values.critical_error_channels = $values.critical_error_channels.filter((entry: any) => {
|
||||
@@ -283,19 +276,54 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setupSnowflakeUrls() {
|
||||
// strip all whitespaces from account identifier
|
||||
snowflakeAccountIdentifier = snowflakeAccountIdentifier.replace(/\s/g, '')
|
||||
// Per-instance OAuth providers (Snowflake, ServiceNow, …) keyed by name ->
|
||||
// their registry connect_config_template. Adding a new one needs only a
|
||||
// registry entry — no code here.
|
||||
const connectConfigTemplates: Record<string, any> = Object.fromEntries(
|
||||
Object.entries(oauthConnectRegistry)
|
||||
.filter(([, cfg]) => cfg && typeof cfg === 'object' && 'connect_config_template' in cfg)
|
||||
.map(([name, cfg]) => [name, (cfg as any).connect_config_template])
|
||||
)
|
||||
|
||||
const connect_config = {
|
||||
scopes: [],
|
||||
auth_url: `https://${snowflakeAccountIdentifier}.snowflakecomputing.com/oauth/authorize`,
|
||||
token_url: `https://${snowflakeAccountIdentifier}.snowflakecomputing.com/oauth/token-request`,
|
||||
req_body_auth: false,
|
||||
extra_params: { account_identifier: snowflakeAccountIdentifier },
|
||||
extra_params_callback: {}
|
||||
function normalizeInstanceInput(tmpl: any, raw: string): string {
|
||||
let v = (raw ?? '').replace(/\s/g, '')
|
||||
if (tmpl.strip_suffix) {
|
||||
// accept a full host/URL or a bare name -> reduce to the bare instance
|
||||
v = v.replace(/^https?:\/\//, '').replace(/\/.*$/, '')
|
||||
if (v.endsWith(tmpl.strip_suffix)) {
|
||||
v = v.slice(0, -tmpl.strip_suffix.length)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Build each per-instance provider's connect_config from the admin-entered
|
||||
// instance name + its registry template (substituting {instance} into the
|
||||
// URLs). Replaces the old per-provider setup functions.
|
||||
function setupTemplatedOauthUrls() {
|
||||
for (const [name, tmpl] of Object.entries(connectConfigTemplates)) {
|
||||
if (!oauths?.[name]) continue
|
||||
const key = tmpl.extra_params_key ?? 'instance'
|
||||
const v = normalizeInstanceInput(tmpl, instanceInputs[name] ?? '')
|
||||
instanceInputs[name] = v
|
||||
if (oauths[name].connect_config?.extra_params?.[key] === v) continue
|
||||
oauths[name].connect_config = {
|
||||
scopes: [],
|
||||
auth_url: tmpl.auth_url.replaceAll('{instance}', v),
|
||||
token_url: tmpl.token_url.replaceAll('{instance}', v),
|
||||
req_body_auth: tmpl.req_body_auth ?? false,
|
||||
extra_params: { [key]: v },
|
||||
extra_params_callback: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recover the instance-name inputs from a saved oauths config (for load/discard).
|
||||
function loadInstanceInputs(savedOauths: Record<string, any>) {
|
||||
for (const [name, tmpl] of Object.entries(connectConfigTemplates)) {
|
||||
const key = tmpl.extra_params_key ?? 'instance'
|
||||
instanceInputs[name] = savedOauths?.[name]?.connect_config?.extra_params?.[key] ?? ''
|
||||
}
|
||||
oauths['snowflake_oauth'].connect_config = connect_config
|
||||
}
|
||||
|
||||
let sendingStats = $state(false)
|
||||
@@ -510,9 +538,7 @@
|
||||
if (category === 'Auth/OAuth/SAML') {
|
||||
oauths = JSON.parse(JSON.stringify(initialOauths))
|
||||
requirePreexistingUserForOauth = initialRequirePreexistingUserForOauth
|
||||
const account_identifier =
|
||||
initialOauths?.snowflake_oauth?.connect_config?.extra_params?.account_identifier
|
||||
snowflakeAccountIdentifier = account_identifier ?? ''
|
||||
loadInstanceInputs(initialOauths)
|
||||
} else if (category === 'Registries') {
|
||||
const v = initialValues['workspace_registries']
|
||||
$values['workspace_registries'] = v !== undefined ? JSON.parse(JSON.stringify(v)) : undefined
|
||||
@@ -524,9 +550,7 @@
|
||||
$values = JSON.parse(JSON.stringify(initialValues))
|
||||
oauths = JSON.parse(JSON.stringify(initialOauths))
|
||||
requirePreexistingUserForOauth = initialRequirePreexistingUserForOauth
|
||||
const account_identifier =
|
||||
initialOauths?.snowflake_oauth?.connect_config?.extra_params?.account_identifier
|
||||
snowflakeAccountIdentifier = account_identifier ?? ''
|
||||
loadInstanceInputs(initialOauths)
|
||||
if (yamlMode) {
|
||||
syncFormToYaml()
|
||||
}
|
||||
@@ -535,13 +559,7 @@
|
||||
export async function saveCategorySettings(category: string) {
|
||||
// Category-specific pre-processing
|
||||
if (category === 'Auth/OAuth/SAML') {
|
||||
if (
|
||||
oauths?.snowflake_oauth &&
|
||||
oauths?.snowflake_oauth?.connect_config?.extra_params?.account_identifier !==
|
||||
snowflakeAccountIdentifier
|
||||
) {
|
||||
setupSnowflakeUrls()
|
||||
}
|
||||
setupTemplatedOauthUrls()
|
||||
}
|
||||
|
||||
if (category === 'Alerts' && $values?.critical_error_channels) {
|
||||
@@ -1116,7 +1134,7 @@
|
||||
{:else if category == 'Auth/OAuth/SAML'}
|
||||
<AuthSettings
|
||||
bind:oauths
|
||||
bind:snowflakeAccountIdentifier
|
||||
bind:instanceInputs
|
||||
bind:requirePreexistingUserForOauth
|
||||
baseUrl={$values?.base_url}
|
||||
bind:tab={authSubTab}
|
||||
|
||||
Reference in New Issue
Block a user