Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f0579c8
initial commit
juliusgeo Apr 8, 2026
542eef6
make it work
juliusgeo Apr 8, 2026
7815dfb
updates
juliusgeo Apr 9, 2026
0389c89
some refactors
juliusgeo Apr 10, 2026
346f70d
Renames and add logo
juliusgeo Apr 10, 2026
01252f0
use email for user login flow
juliusgeo Apr 10, 2026
c55ca3f
refine login page
juliusgeo Apr 10, 2026
4b7bf50
fix toast not showing up
juliusgeo Apr 12, 2026
76eb088
merge
juliusgeo Apr 13, 2026
8f309d8
remove shadcn components
juliusgeo Apr 13, 2026
ad253bc
use openapi methods
juliusgeo Apr 13, 2026
cc019ab
use cloud metadata querY
juliusgeo Apr 13, 2026
78ae76d
remove comment
juliusgeo Apr 13, 2026
cddbcb2
adjust names and descriptions for openapi, switch to upsert not create
juliusgeo Apr 13, 2026
d4fd80c
conditionally show SSO org page
juliusgeo Apr 14, 2026
7ec432f
fix issues when SSO isn't enabled
juliusgeo Apr 14, 2026
5aed16a
refactor to use react stuff
juliusgeo Apr 14, 2026
e47288c
fix lint issues
juliusgeo Apr 14, 2026
c92f868
fix build
juliusgeo Apr 14, 2026
34a7045
fix lint again
juliusgeo Apr 14, 2026
3f5e3ce
Merge branch 'main' into feat_propelauth_sso
juliusgeo Apr 14, 2026
ab1972e
fix more lint
juliusgeo Apr 14, 2026
2a7ce4c
fix events links
juliusgeo Apr 14, 2026
fe7d4d3
rm generated changes
juliusgeo Apr 14, 2026
7141235
merge
juliusgeo Apr 14, 2026
ae3c22f
fix broken organization page error
juliusgeo Apr 14, 2026
135a23b
remove hatchet cloud generated api
juliusgeo Apr 16, 2026
8644a07
Merge branch 'main' into feat_propelauth_sso
juliusgeo Apr 16, 2026
e03c950
switch to control-plane api
juliusgeo Apr 16, 2026
917380a
Merge branch 'main' into feat_propelauth_sso
juliusgeo Apr 16, 2026
e601559
fixes
juliusgeo Apr 16, 2026
1c06ae1
fix build issues
juliusgeo Apr 16, 2026
efc37ef
generate
juliusgeo Apr 16, 2026
48e64ba
handle error better for domains
juliusgeo Apr 17, 2026
909791c
merge
juliusgeo Apr 21, 2026
31d76de
Merge branch 'main' into feat_propelauth_sso
juliusgeo Apr 21, 2026
8a58ff5
update for new sso domain methods
juliusgeo Apr 21, 2026
d8ddcb5
fix renamed methods
juliusgeo Apr 21, 2026
f0ef49f
Merge branch 'main' into feat_propelauth_sso
juliusgeo Apr 21, 2026
833d6bd
updates to work with new domain verification stuff
juliusgeo Apr 21, 2026
6ad1512
more changes
juliusgeo Apr 22, 2026
76af2d4
Merge branch 'main' into feat_propelauth_sso
juliusgeo Apr 23, 2026
1ebfcd6
Merge branch 'main' into feat_propelauth_sso
juliusgeo Apr 23, 2026
03289d4
fix all the issues hopefully
juliusgeo Apr 23, 2026
107f506
fix lint
juliusgeo Apr 23, 2026
b6865ea
merge main
juliusgeo Apr 23, 2026
dffa0ac
gate behind organization ownership
juliusgeo Apr 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
48 changes: 48 additions & 0 deletions frontend/app/src/components/sso/SsoDeleteConfirmationDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Button } from '@/components/v1/ui/button';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/v1/ui/dialog';
import { useSsoDeleteConfirmation } from '@/hooks/sso/SsoSetupHooks';
import { Loader2 } from 'lucide-react';

export function SsoDeleteConfirmationDialog() {
const { open, onOpenChange, onConfirm, deleting, providerName } =
useSsoDeleteConfirmation();
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete SSO Configuration?</DialogTitle>
<DialogDescription>
This will permanently delete the SSO configuration for{' '}
<strong>{providerName}</strong>. This action cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={deleting}
>
Cancel
</Button>
<Button variant="destructive" onClick={onConfirm} disabled={deleting}>
{deleting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Deleting...
</>
) : (
'Delete Configuration'
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
16 changes: 16 additions & 0 deletions frontend/app/src/components/sso/SsoErrorAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Alert, AlertDescription } from '@/components/v1/ui/alert';
import { useSsoErrorAlert } from '@/hooks/sso/SsoSetupHooks';

export function SsoErrorAlert() {
const { message } = useSsoErrorAlert();

if (!message) {
return null;
}

return (
<Alert variant="destructive">
<AlertDescription>{message}</AlertDescription>
</Alert>
);
}
8 changes: 8 additions & 0 deletions frontend/app/src/components/sso/SsoErrorText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';

export function SsoErrorText({ children }: { children?: React.ReactNode }) {
if (!children) {
return null;
}
return <p className="text-xs text-destructive">{children}</p>;
}
22 changes: 22 additions & 0 deletions frontend/app/src/components/sso/SsoField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Label } from '@/components/v1/ui/label';

export function SsoField({
label,
htmlFor,
children,
required,
}: {
label: string;
htmlFor?: string;
children: React.ReactNode;
required?: boolean;
}) {
return (
<div className="grid gap-1.5">
<Label htmlFor={htmlFor}>
{label} {required && <span className="text-destructive">*</span>}
</Label>
{children}
</div>
);
}
39 changes: 39 additions & 0 deletions frontend/app/src/components/sso/SsoFormInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { SsoErrorText } from './SsoErrorText';
import { Input } from '@/components/v1/ui/input';

export function SsoFormInput({
id,
type = 'text',
placeholder,
value,
onChange,
error,
readOnly,
autoComplete,
...dataAttributes
}: {
id: string;
type?: string;
placeholder?: string;
value: string;
onChange: (value: string) => void;
error?: string;
readOnly?: boolean;
autoComplete?: string;
} & Record<`data-${string}`, string | boolean | undefined>) {
return (
<>
<Input
id={id}
type={type}
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.target.value)}
readOnly={readOnly}
autoComplete={autoComplete}
{...dataAttributes}
/>
{error && <SsoErrorText>{error}</SsoErrorText>}
</>
);
}
24 changes: 24 additions & 0 deletions frontend/app/src/components/sso/SsoIdpPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Button } from '@/components/v1/ui/button';
import { useSsoIdpPicker } from '@/hooks/sso/SsoSetupHooks';
import { PROVIDER_CONFIG } from '@/lib/sso/sso-constants';
import { ProviderKey } from '@/lib/sso/sso-types';

export function SsoIdpPicker() {
const { onProviderSelect } = useSsoIdpPicker();
const providers = Object.keys(PROVIDER_CONFIG) as ProviderKey[];

return (
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
{providers.map((p) => (
<Button
key={p}
type="button"
onClick={() => onProviderSelect(p)}
className="cursor-pointer"
>
<span className="font-medium">{PROVIDER_CONFIG[p].displayName}</span>
</Button>
))}
</div>
);
}
11 changes: 11 additions & 0 deletions frontend/app/src/components/sso/SsoLoadingState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Loader2 } from 'lucide-react';

export function SsoLoadingState() {
return (
<div className="mx-auto max-w-3xl">
<div className="flex h-32 items-center justify-center text-muted-foreground">
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Loading…
</div>
</div>
);
}
19 changes: 19 additions & 0 deletions frontend/app/src/components/sso/SsoPkceRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Switch } from '@/components/v1/ui/switch';

export function SsoPkceRow({
checked,
onChange,
}: {
checked: boolean;
onChange: (v: boolean) => void;
}) {
return (
<div className="mt-2 flex items-center justify-between rounded-xl border p-3">
<div className="space-y-0.5">
<div className="text-sm font-medium">Use PKCE</div>
<p className="text-xs text-muted-foreground">Recommended.</p>
</div>
<Switch checked={checked} onCheckedChange={onChange} />
</div>
);
}
129 changes: 129 additions & 0 deletions frontend/app/src/components/sso/SsoProviderForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { SsoField } from './SsoField';
import { SsoFormInput } from './SsoFormInput';
import { SsoPkceRow } from './SsoPkceRow';
import { useSsoProviderForm } from '@/hooks/sso/SsoSetupHooks';

export function SsoProviderForm() {
const { form, errors, onChange, isEditMode } = useSsoProviderForm();
// Common fields for all providers
const commonSsoFields = (
<>
<SsoField label="Client ID" htmlFor="clientId" required>
<SsoFormInput
id="clientId"
placeholder="client_id"
value={form.clientId}
onChange={(v) => onChange('clientId', v)}
error={errors.clientId}
readOnly={isEditMode}
autoComplete="off"
data-1p-ignore
data-bwignore
data-lpignore="true"
data-protonpass-ignore="true"
data-form-type="other"
/>
</SsoField>
<SsoField
label="Client Secret"
htmlFor="clientSecret"
required={!isEditMode}
>
<SsoFormInput
id="clientSecret"
type="password"
placeholder={isEditMode ? 'Leave empty to keep existing' : '••••••••'}
value={form.clientSecret}
onChange={(v) => onChange('clientSecret', v)}
error={errors.clientSecret}
autoComplete="off"
data-1p-ignore
data-bwignore
data-lpignore="true"
data-protonpass-ignore="true"
data-form-type="other"
/>
</SsoField>
</>
);

if (form.provider === 'Okta') {
return (
<>
<SsoField label="SSO Domain" htmlFor="ssoDomain" required>
<SsoFormInput
id="ssoDomain"
placeholder="example.okta.com"
value={form.ssoDomain || ''}
onChange={(v) => onChange('ssoDomain', v)}
error={errors.ssoDomain}
/>
</SsoField>
{commonSsoFields}
<SsoPkceRow
checked={form.usesPkce}
onChange={(v) => onChange('usesPkce', v)}
/>
</>
);
}

if (form.provider === 'MicrosoftEntra') {
return (
<>
<SsoField label="Tenant ID" htmlFor="tenantId" required>
<SsoFormInput
id="tenantId"
placeholder="00000000-0000-0000-0000-000000000000"
value={form.tenantId || ''}
onChange={(v) => onChange('tenantId', v)}
error={errors.tenantId}
/>
</SsoField>
{commonSsoFields}
<SsoPkceRow
checked={form.usesPkce}
onChange={(v) => onChange('usesPkce', v)}
/>
</>
);
}

// Generic providers (Google, OneLogin, JumpCloud, Generic)
return (
<>
{commonSsoFields}
<SsoField label="Authorize URL" htmlFor="authUrl" required>
<SsoFormInput
id="authUrl"
placeholder="https://.../authorize"
value={form.authUrl || ''}
onChange={(v) => onChange('authUrl', v)}
error={errors.authUrl}
/>
</SsoField>
<SsoField label="Token URL" htmlFor="tokenUrl" required>
<SsoFormInput
id="tokenUrl"
placeholder="https://.../token"
value={form.tokenUrl || ''}
onChange={(v) => onChange('tokenUrl', v)}
error={errors.tokenUrl}
/>
</SsoField>
<SsoField label="Userinfo URL" htmlFor="userinfoUrl" required>
<SsoFormInput
id="userinfoUrl"
placeholder="https://.../userinfo"
value={form.userinfoUrl || ''}
onChange={(v) => onChange('userinfoUrl', v)}
error={errors.userinfoUrl}
/>
</SsoField>
<SsoPkceRow
checked={form.usesPkce}
onChange={(v) => onChange('usesPkce', v)}
/>
</>
);
}
35 changes: 35 additions & 0 deletions frontend/app/src/components/sso/SsoRedirectUriField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { SsoField } from './SsoField';
import { Button } from '@/components/v1/ui/button';
import { Input } from '@/components/v1/ui/input';
import { copySsoToClipboard } from '@/lib/sso/sso-utils';
import { Check, Copy } from 'lucide-react';
import { useState } from 'react';

export function SsoRedirectUriField({ redirectUrl }: { redirectUrl: string }) {
const [copied, setCopied] = useState(false);

return (
<SsoField label="Redirect / Callback URL">
<div className="flex items-center gap-2">
<Input readOnly value={redirectUrl} tabIndex={-1} />
<Button
type="button"
size="sm"
onClick={() => {
copySsoToClipboard(redirectUrl, () => {
setCopied(true);
setTimeout(() => setCopied(false), 500);
});
}}
className="shrink-0 cursor-pointer"
>
{copied ? (
<Check className="h-4 w-4" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
</div>
</SsoField>
);
}
Loading
Loading