Skip to content

Automatic ACS URL in IdP forms w/ copy button #2510

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

Merged
merged 21 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
13 changes: 13 additions & 0 deletions app/forms/idp/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ import { pb } from '~/util/path-builder'

import { MetadataSourceField, type IdpCreateFormValues } from './shared'

const host = window.document.location.host.toString()
const subdomain = host.includes('localhost')
? 'r3.oxide-preview.com'
: host.split('.').slice(2).join('.')

const defaultValues: IdpCreateFormValues = {
type: 'saml',
name: '',
Expand Down Expand Up @@ -59,6 +64,7 @@ export function CreateIdpSideModalForm() {
})

const form = useForm({ defaultValues })
const name = form.watch('name')

return (
<SideModalForm
Expand Down Expand Up @@ -111,8 +117,15 @@ export function CreateIdpSideModalForm() {
name="acsUrl"
label="ACS URL"
description="Service provider endpoint for the IdP to send the SAML response"
// When creating a SAML identity provider connection, the ACS URL that the user enters
// should always be of the form: http(s)://<silo>.sys.<subdomain>/login/<silo>/saml/<name>
// where <silo> is the Silo name, <subdomain> is the subdomain assigned to the rack,
// and <name> is the name of the IdP connection
value={`https://${silo}.sys.${subdomain}/login/${silo}/saml/${name}`}
required
disabled
control={form.control}
copyable
/>
{/* TODO: help text */}
<TextField name="idpEntityId" label="Entity ID" required control={form.control} />
Expand Down
1 change: 1 addition & 0 deletions app/forms/idp/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export function EditIdpSideModalForm() {
required
control={form.control}
disabled
copyable
/>
{/* TODO: help text */}
<TextField
Expand Down
54 changes: 39 additions & 15 deletions app/ui/lib/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { announce } from '@react-aria/live-announcer'
import cn from 'classnames'
import React, { useEffect } from 'react'

import { CopyToClipboard } from './CopyToClipboard'
import { Tooltip } from './Tooltip'

/**
* This is a little complicated. We only want to allow the `rows` prop if
* `as="textarea"`. But the derivatives of `TextField`, like `NameField`, etc.,
Expand Down Expand Up @@ -38,6 +41,7 @@ export type TextInputBaseProps = React.ComponentPropsWithRef<'input'> & {
disabled?: boolean
className?: string
fieldClassName?: string
copyable?: boolean
}

export const TextInput = React.forwardRef<
Expand All @@ -47,41 +51,61 @@ export const TextInput = React.forwardRef<
(
{
type = 'text',
value,
error,
className,
disabled,
fieldClassName,
copyable,
as: asProp,
...fieldProps
},
ref
) => {
const Component = asProp || 'input'
const component = (
<Component
// @ts-expect-error this is fine, it's just mad because Component is a variable
ref={ref}
type={type}
value={value}
className={cn(
`w-full rounded border-none px-3 py-[0.6875rem] !outline-offset-1 text-sans-md text-default bg-default placeholder:text-quaternary focus:outline-none disabled:cursor-not-allowed disabled:text-tertiary disabled:bg-disabled`,
error && 'focus-error',
fieldClassName,
disabled && 'text-disabled bg-disabled'
)}
aria-invalid={error}
disabled={disabled}
{...fieldProps}
/>
)
const copyableValue = value?.toString() || ''
return (
<div
className={cn(
'flex rounded border',
'flex items-center rounded border',
error
? 'border-error-secondary hover:border-error'
: 'border-default hover:border-hover',
disabled && '!border-default',
className
)}
>
<Component
// @ts-expect-error this is fine, it's just mad because Component is a variable
ref={ref}
type={type}
className={cn(
`w-full rounded border-none px-3 py-[0.6875rem] !outline-offset-1 text-sans-md text-default bg-default placeholder:text-quaternary focus:outline-none disabled:cursor-not-allowed disabled:text-tertiary disabled:bg-disabled`,
error && 'focus-error',
fieldClassName,
disabled && 'text-disabled bg-disabled'
)}
aria-invalid={error}
disabled={disabled}
{...fieldProps}
/>
{/* don't bother with the tooltip if the string is short */}
{copyable && copyableValue.length > 50 ? (
<Tooltip content={copyableValue} placement="top">
{component}
</Tooltip>
) : (
component
)}
{copyable && (
<CopyToClipboard
text={copyableValue}
className="flex h-full items-stretch border-l border-solid px-3 border-default hover:border-hover"
/>
)}
</div>
)
}
Expand Down
Loading