-
Notifications
You must be signed in to change notification settings - Fork 27
Add support k8s static auth #372
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
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
653d7a9
Add support for static k8s provider
celdrake 5de39e8
Handle k8s token expired
celdrake ebba7e8
Use lestrrat-go/jwx to obtain the info from token claims
celdrake f8ee7ca
Displaying better error messages
celdrake 2d3fdb1
Adapt to new AuthConfig response for the default provider
celdrake File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| 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 { ORGANIZATION_STORAGE_KEY } from '@flightctl/ui-components/src/utils/organizationStorage'; | ||
| import FlightCtlForm from '@flightctl/ui-components/src/components/form/FlightCtlForm'; | ||
| import { useTranslation } from '@flightctl/ui-components/src/hooks/useTranslation'; | ||
| 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<string>(''); | ||
| const [isSubmitting, setIsSubmitting] = React.useState(false); | ||
| const [submitError, setSubmitError] = React.useState<string>(); | ||
|
|
||
| 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) { | ||
| let errorMessage = t('Authentication failed'); | ||
| try { | ||
| const contentType = resp.headers.get('content-type'); | ||
| if (contentType && contentType.includes('application/json')) { | ||
| const errorData = (await resp.json()) as { error?: string }; | ||
| errorMessage = errorData.error || errorMessage; | ||
| } else { | ||
| // Fallback for non-JSON responses | ||
| const text = await resp.text(); | ||
| if (text) { | ||
| errorMessage = text; | ||
| } | ||
| } | ||
| } catch (parseErr) { | ||
| // If parsing fails, use default error message | ||
| errorMessage = t('Authentication failed'); | ||
| } | ||
| setSubmitError(errorMessage); | ||
| 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 ( | ||
| <Bullseye> | ||
| <Card isLarge> | ||
| <CardBody> | ||
| <Stack hasGutter style={{ '--pf-v5-l-stack--m-gutter--Gap': '1.5rem' } as React.CSSProperties}> | ||
| <StackItem> | ||
| <Title headingLevel="h2" size="xl"> | ||
| {t('Enter your Kubernetes token')} | ||
| </Title> | ||
| </StackItem> | ||
|
|
||
| <StackItem> | ||
| <TextContent> | ||
| <Text>{t('Enter your Kubernetes service account token to authenticate with the cluster.')}</Text> | ||
| <Text component={TextVariants.small}> | ||
| {t('You can find this token in your Kubernetes service account credentials.')} | ||
| </Text> | ||
| </TextContent> | ||
| </StackItem> | ||
| <StackItem> | ||
| <FlightCtlForm> | ||
| <FormGroup label={t('Service account token')} isRequired> | ||
| <TextArea | ||
| id="accessToken" | ||
| value={token} | ||
| onChange={(_event, tokenVal) => { | ||
| if (tokenVal && !isValidJwtTokenFormat(tokenVal)) { | ||
| setValidationError( | ||
| t('Invalid token format. Expected a JWT token with format: header.payload.signature'), | ||
| ); | ||
| } else { | ||
| setValidationError(''); | ||
| } | ||
| if (submitError) { | ||
| setSubmitError(undefined); | ||
| } | ||
| setToken(tokenVal); | ||
| }} | ||
| placeholder={t('Enter your Kubernetes token...')} | ||
| rows={10} | ||
| isRequired | ||
| isDisabled={isSubmitting} | ||
| autoFocus | ||
| validated={validationError ? 'error' : 'default'} | ||
| /> | ||
| {validationError && ( | ||
| <FormHelperText> | ||
| <HelperText> | ||
| <HelperTextItem variant="error">{validationError}</HelperTextItem> | ||
| </HelperText> | ||
| </FormHelperText> | ||
| )} | ||
| </FormGroup> | ||
|
|
||
| <Alert variant="warning" title={t('Keep your token secure')} isInline> | ||
| {t( | ||
| 'Never share your service account token. It provides full access to your Kubernetes cluster resources.', | ||
| )} | ||
| </Alert> | ||
|
|
||
| {submitError && ( | ||
| <FormSection> | ||
| <Alert variant="danger" title={t('Authentication failed')} isInline> | ||
| {submitError} | ||
| </Alert> | ||
| </FormSection> | ||
| )} | ||
| </FlightCtlForm> | ||
| </StackItem> | ||
| </Stack> | ||
| </CardBody> | ||
| <CardFooter> | ||
| <Button | ||
| variant="primary" | ||
| isDisabled={!token || !!validationError || isSubmitting} | ||
| isLoading={isSubmitting} | ||
| onClick={handleSubmit} | ||
| > | ||
| {isSubmitting ? t('Authenticating...') : t('Login')} | ||
| </Button> | ||
| </CardFooter> | ||
| </Card> | ||
| </Bullseye> | ||
| ); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should show the error somewhere ? Otherwise it will be really hard to debug
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll do.
In the full branch the UI proxy returns different error messages and the UI shows them, but it's also worth having it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the invalid/expired token. Will address more use cases if needed in the full branch.