Skip to content

Add Cloudflare Access authentication settings#348

Open
pius-pp wants to merge 34 commits into
remnawave:devfrom
pius-pp:cloudflare-access
Open

Add Cloudflare Access authentication settings#348
pius-pp wants to merge 34 commits into
remnawave:devfrom
pius-pp:cloudflare-access

Conversation

@pius-pp
Copy link
Copy Markdown

@pius-pp pius-pp commented May 24, 2026

Summary

Adds UI support for Cloudflare Access authentication.

This wires the frontend to the new backend Cloudflare Access auth method, adds settings controls, and automatically attempts Cloudflare Access login when the method is enabled.

Changes

  • Add Cloudflare Access section to authentication settings
  • Add controls for:
    • enabled
    • team domain
    • audience
    • email allowlist toggle
    • allowed emails
    • allowed domains
  • Parse Cloudflare Access fields in Remnawave settings responses
  • Parse Cloudflare Access availability in auth status
  • Add Cloudflare Access login hook
  • Automatically attempt Cloudflare Access login on the login page when enabled

Notes

The frontend currently extends the published backend contract schemas locally so the new fields are preserved before the backend contract package is released with these additions.

Validation

  • git diff --check

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 24, 2026

Greptile Summary

Wires Cloudflare Access as a new auth method across the frontend: new settings controls (team domain, audience, email allowlist, allowed emails/domains) are added to the authentication settings card, and the login page automatically attempts a Cloudflare Access token exchange on load when the method is enabled. New Zod schemas locally extend the published backend contract to carry the extra fields until the upstream package ships.

  • Login page auto-flow: a one-shot useEffect fires the Cloudflare Access POST on mount; setToken + setIsAuthenticated(true) on success follow the same pattern used by password login. On failure the attempt ref stays true, so there is no automatic or user-triggered retry — a page refresh is required.
  • Settings widget: the new accordion section exposes all Cloudflare Access fields using the existing CheckboxCardShared and TagsInput components; team domain and audience inputs accept arbitrary strings without client-side format guards.
  • Schema extension pattern: both the auth-status and remnawave-settings response schemas are extended with .default(...) values, so the frontend degrades cleanly against a backend that does not yet return the new fields.

Confidence Score: 4/5

The change is safe to merge. The new auth flow is additive and degrades cleanly on backends that do not yet expose Cloudflare Access fields.

The Cloudflare Access auto-login on the login page permanently gates retries with a ref, so a transient failure leaves users with only a red badge and no actionable path forward (apart from refreshing). The schema extension approach and dual-callback onSuccess pattern are consistent with the rest of the codebase.

src/pages/auth/login/login.page.tsx — the one-shot login attempt and missing retry path deserve a second look before shipping to users who may be behind Cloudflare Access exclusively.

Important Files Changed

Filename Overview
src/pages/auth/login/login.page.tsx Adds automatic Cloudflare Access login attempt on page load via a one-shot useEffect; once the attempt fires the ref gates all retries, leaving users with a failure badge and no recovery path
src/shared/api/hooks/auth/auth.hooks.ts Adds useCloudflareAccessLogin mutation hook with inline Zod schemas; correctly follows the same token-setting pattern as other auth hooks
src/shared/api/hooks/auth/auth.query.hooks.ts Extends GetStatusCommand.ResponseSchema to include cloudflareAccess with a safe .default({ enabled: false }), so existing backends that omit the field still parse cleanly
src/shared/api/hooks/remnawave-settings/remnawave-settings.mutation.hooks.ts Exports CloudflareAccessSettingsSchema, extends request/response schemas for Cloudflare Access settings; clean and consistent with the codebase pattern
src/shared/api/hooks/remnawave-settings/remnawave-settings.query.hooks.ts Extends GetRemnawaveSettingsCommand.ResponseSchema with nullable cloudflareAccessSettings; defaults to null for backwards compatibility with older backends
src/pages/dashboard/remnawave-settings/components/remnawave-settings.page.component.tsx Passes cloudflareAccessSettings down to the auth settings widget via a type assertion cast; functional but slightly fragile if the type shape drifts from the schema
src/widgets/remnawave-settings/authentification-settings-card/authentification-settings-card.widget.tsx Adds Cloudflare Access accordion section with enable toggle, team domain, audience, email allowlist toggle, allowed emails, and allowed domains inputs; inputs lack format validation for structured fields

Sequence Diagram

sequenceDiagram
    participant Browser
    participant LoginPage
    participant useGetAuthStatus
    participant useCloudflareAccessLogin
    participant Backend

    Browser->>LoginPage: mount
    LoginPage->>useGetAuthStatus: GET /api/status
    Backend-->>useGetAuthStatus: "{ authentication: { cloudflareAccess: { enabled: true } } }"
    useGetAuthStatus-->>LoginPage: authStatus (with cloudflareAccess)

    Note over LoginPage: useEffect fires<br/>isCloudflareAccessEnabled=true<br/>isAttempted=false → attempt once

    LoginPage->>useCloudflareAccessLogin: "POST /api/auth/cloudflare-access {}"
    Backend-->>useCloudflareAccessLogin: "{ response: { accessToken: ... } }"

    alt Success
        useCloudflareAccessLogin-->>LoginPage: onSuccess → setToken + setIsAuthenticated(true)
        LoginPage->>Browser: AuthGuard redirects to dashboard
    else Failure
        useCloudflareAccessLogin-->>LoginPage: onError → setIsCloudflareAccessFailed(true)
        Note over LoginPage: Red badge shown, ref=true<br/>no retry possible, must refresh
    end
Loading

Comments Outside Diff (1)

  1. src/pages/auth/login/login.page.tsx, line 15-44 (link)

    P2 Local type definitions duplicate the extended query schema

    TAuthStatusAuthentication and TAuthStatusWithCloudflareAccess manually re-describe what GetStatusResponseSchema (in auth.query.hooks.ts) already expresses structurally. The getAuthMethods function then casts authStatus to these local types to access cloudflareAccess. If the schema in auth.query.hooks.ts evolves (e.g., adding more fields, changing enabled to an optional), these local types silently diverge. Using the inferred type from the extended schema directly — or accepting the hook return type as the function parameter — would remove the duplication.

Reviews (1): Last reviewed commit: "Add Cloudflare Access authentication" | Re-trigger Greptile

Comment on lines +141 to +153
useEffect(() => {
if (
!authStatus?.isLoginAllowed ||
!authMethods.isCloudflareAccessEnabled ||
isCloudflareAccessAttemptedRef.current
) {
return
}

isCloudflareAccessAttemptedRef.current = true
setIsCloudflareAccessFailed(false)
cloudflareAccessLogin({ variables: {} })
}, [authStatus?.isLoginAllowed, authMethods.isCloudflareAccessEnabled, cloudflareAccessLogin])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 No retry path after Cloudflare Access failure

Once isCloudflareAccessAttemptedRef.current is set to true, the effect is permanently gated and will never fire again in the same component instance. If the initial automatic login request fails (e.g., network hiccup, CF misconfiguration), the red badge appears but there is no way to reattempt login short of a full page refresh. When Cloudflare Access is the only configured auth method, the user is left with no actionable path forward — no retry button, no other form visible.

Comment on lines +557 to +580
/>
</Group>
</Center>

<Accordion.Panel>
<Stack gap="md">
<TextInput
description="Cloudflare Access team domain, for example team.cloudflareaccess.com"
key={form.key('cloudflareAccessSettings.teamDomain')}
label="Team domain"
placeholder="team.cloudflareaccess.com"
{...form.getInputProps(
'cloudflareAccessSettings.teamDomain'
)}
/>

<TextInput
description="Cloudflare Access application AUD tag"
key={form.key('cloudflareAccessSettings.audience')}
label="Audience"
placeholder="0000000000000000000000000000000000000000000000000000000000000000"
{...form.getInputProps(
'cloudflareAccessSettings.audience'
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 No format validation on teamDomain and audience inputs

teamDomain and audience are free-text fields with no client-side format constraints. A user can save an arbitrary string as the team domain (should look like <team>.cloudflareaccess.com or a custom domain) or as the audience (should be a 64-character hex string). While the backend should ultimately validate these, adding pattern/regex validation in CloudflareAccessSettingsSchema would surface misconfiguration earlier and keep the UX consistent with how other structured fields are handled in the form.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants