From 653d7a9be19c68c0b89af73e9263139b7948edb7 Mon Sep 17 00:00:00 2001 From: Celia Amador Date: Fri, 7 Nov 2025 09:59:15 +0100 Subject: [PATCH 1/5] Add support for static k8s provider --- .../src/app/components/Login/LoginPage.tsx | 175 +++++++++++++ .../standalone/src/app/context/AuthContext.ts | 6 + apps/standalone/src/app/routes.tsx | 5 + apps/standalone/src/app/utils/apiCalls.ts | 4 +- libs/types/index.ts | 20 +- libs/types/models/AapProviderSpec.ts | 34 +++ libs/types/models/AuthConfig.ts | 22 +- .../AuthDynamicOrganizationAssignment.ts | 26 ++ .../types/models/AuthDynamicRoleAssignment.ts | 18 ++ .../models/AuthOrganizationAssignment.ts | 12 + libs/types/models/AuthOrganizationsConfig.ts | 14 - .../AuthPerUserOrganizationAssignment.ts | 22 ++ libs/types/models/AuthProvider.ts | 22 ++ libs/types/models/AuthProviderList.ts | 25 ++ libs/types/models/AuthProviderSpec.ts | 10 + libs/types/models/AuthRoleAssignment.ts | 11 + .../AuthStaticOrganizationAssignment.ts | 18 ++ libs/types/models/AuthStaticRoleAssignment.ts | 18 ++ libs/types/models/DeviceStatus.ts | 7 +- libs/types/models/K8sProviderSpec.ts | 38 +++ libs/types/models/OAuth2ProviderSpec.ts | 58 +++++ libs/types/models/OIDCProviderSpec.ts | 46 ++++ libs/types/models/ResourceKind.ts | 1 + libs/types/models/SystemdActiveStateType.ts | 17 ++ libs/types/models/SystemdEnableStateType.ts | 22 ++ libs/types/models/SystemdLoadStateType.ts | 16 ++ libs/types/models/SystemdUnitStatus.ts | 25 ++ proxy/auth/auth.go | 28 ++ proxy/auth/common.go | 19 ++ proxy/auth/k8s.go | 239 ++++++++++++++++++ 30 files changed, 951 insertions(+), 27 deletions(-) create mode 100644 apps/standalone/src/app/components/Login/LoginPage.tsx create mode 100644 libs/types/models/AapProviderSpec.ts create mode 100644 libs/types/models/AuthDynamicOrganizationAssignment.ts create mode 100644 libs/types/models/AuthDynamicRoleAssignment.ts create mode 100644 libs/types/models/AuthOrganizationAssignment.ts delete mode 100644 libs/types/models/AuthOrganizationsConfig.ts create mode 100644 libs/types/models/AuthPerUserOrganizationAssignment.ts create mode 100644 libs/types/models/AuthProvider.ts create mode 100644 libs/types/models/AuthProviderList.ts create mode 100644 libs/types/models/AuthProviderSpec.ts create mode 100644 libs/types/models/AuthRoleAssignment.ts create mode 100644 libs/types/models/AuthStaticOrganizationAssignment.ts create mode 100644 libs/types/models/AuthStaticRoleAssignment.ts create mode 100644 libs/types/models/K8sProviderSpec.ts create mode 100644 libs/types/models/OAuth2ProviderSpec.ts create mode 100644 libs/types/models/OIDCProviderSpec.ts create mode 100644 libs/types/models/SystemdActiveStateType.ts create mode 100644 libs/types/models/SystemdEnableStateType.ts create mode 100644 libs/types/models/SystemdLoadStateType.ts create mode 100644 libs/types/models/SystemdUnitStatus.ts create mode 100644 proxy/auth/k8s.go diff --git a/apps/standalone/src/app/components/Login/LoginPage.tsx b/apps/standalone/src/app/components/Login/LoginPage.tsx new file mode 100644 index 000000000..dc165e8ed --- /dev/null +++ b/apps/standalone/src/app/components/Login/LoginPage.tsx @@ -0,0 +1,175 @@ +import * as React from 'react'; +import { + Alert, + Bullseye, + Button, + Card, + CardBody, + CardFooter, + FormGroup, + FormHelperText, + FormSection, + HelperText, + HelperTextItem, + Stack, + StackItem, + Text, + TextArea, + TextContent, + TextVariants, + Title, +} from '@patternfly/react-core'; +import { useTranslation } from 'react-i18next'; + +import { ORGANIZATION_STORAGE_KEY } from '@flightctl/ui-components/src/utils/organizationStorage'; +import FlightCtlForm from '@flightctl/ui-components/src/components/form/FlightCtlForm'; +import { loginAPI } from '../../utils/apiCalls'; + +const EXPIRATION = 'expiration'; + +const nowInSeconds = () => Math.floor(Date.now() / 1000); + +// Simple JWT format validation - checks if token has 3 parts separated by dots +export const isValidJwtTokenFormat = (token: string): boolean => { + if (!token) return false; + const parts = token.split('.'); + if (parts.length !== 3) return false; + // Check that each part contains only valid base64url characters + const base64urlPattern = /^[A-Za-z0-9_-]+$/; + return parts.every((part) => part.length > 0 && base64urlPattern.test(part)); +}; + +export const LoginPage = () => { + const { t } = useTranslation(); + const [token, setToken] = React.useState(''); + const [validationError, setValidationError] = React.useState(''); + const [isSubmitting, setIsSubmitting] = React.useState(false); + const [submitError, setSubmitError] = React.useState(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setSubmitError(undefined); + setIsSubmitting(true); + + try { + localStorage.removeItem(EXPIRATION); + localStorage.removeItem(ORGANIZATION_STORAGE_KEY); + + const resp = await fetch(loginAPI, { + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + method: 'POST', + body: JSON.stringify({ + token: token.trim(), + }), + }); + + if (!resp.ok) { + const errorData = await resp.json().catch(() => ({})); + setSubmitError(errorData.error || 'Authentication failed'); + setIsSubmitting(false); + return; + } + + const expiration = (await resp.json()) as { expiresIn: number }; + if (expiration.expiresIn) { + const now = nowInSeconds(); + localStorage.setItem(EXPIRATION, `${now + expiration.expiresIn}`); + } + + // Redirect to home page after successful login + window.location.href = '/'; + } catch (err) { + setSubmitError(t('Failed to authenticate. Please check your token and try again.')); + setIsSubmitting(false); + } + }; + + return ( + + + + + + + {t('Enter your Kubernetes token')} + + + + + + {t('Enter your Kubernetes service account token to authenticate with the cluster.')} + + {t('You can find this token in your Kubernetes service account credentials.')} + + + + + + +