-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: separate api auth to plugin folder (#495)
* fix: separate api auth to plugin folder * chore: for local admin URL is undefined * chore: update README.md * test: fix tests + add new ones * v0.0.2 - release for flyte-api plugin Signed-off-by: Nastya <[email protected]>
- Loading branch information
Showing
34 changed files
with
550 additions
and
345 deletions.
There are no files selected for viewing
This file contains 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 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 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 |
---|---|---|
@@ -1 +1,39 @@ | ||
This is a flyte-API package for flyteconsole plugin system | ||
## @flyteconsole/flyte-api | ||
|
||
This package provides ability to do FlyteAdmin API calls from JS/TS code. | ||
|
||
At this point it allows to get though authentication steps, request user profile and FlyteAdmin version. | ||
In future releases we will add ability to do all types of FlyteAdmin API calls. | ||
|
||
### Installation | ||
|
||
To install the package please run: | ||
```bash | ||
yarn add @flyteconsole/flyte-api | ||
``` | ||
|
||
### Usage | ||
|
||
To use in your application | ||
|
||
- Wrap parent component with <FlyteApiProvider flyteApiDomain={ADMIN_API_URL ?? ''}> | ||
|
||
`ADMIN_API_URL` is a flyte admin domain URL to which `/api/v1/_endpoint` part would be added, to perform REST API call. | ||
` | ||
Then from any child component | ||
|
||
```js | ||
import useAxios from 'axios-hooks'; | ||
import { useFlyteApi, defaultAxiosConfig } from '@flyteconsole/flyte-api'; | ||
|
||
... | ||
/** Get profile information */ | ||
const apiContext = useFlyteApi(); | ||
|
||
const profilePath = apiContext.getProfileUrl(); | ||
const [{ data: profile, loading }] = useAxios({ | ||
url: profilePath, | ||
method: 'GET', | ||
...defaultAxiosConfig, | ||
}); | ||
``` |
This file contains 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
47 changes: 47 additions & 0 deletions
47
packages/plugins/flyte-api/src/ApiProvider/apiProvider.test.tsx
This file contains 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,47 @@ | ||
import * as React from 'react'; | ||
import { render } from '@testing-library/react'; | ||
import { FlyteApiProvider, useFlyteApi } from '.'; | ||
import { AdminEndpoint } from '../utils/constants'; | ||
import { getLoginUrl } from './login'; | ||
|
||
const MockCoponent = () => { | ||
const context = useFlyteApi(); | ||
|
||
return ( | ||
<> | ||
<div>{context.getProfileUrl()}</div> | ||
<div>{context.getAdminApiUrl('/magic')}</div> | ||
<div>{context.getLoginUrl()}</div> | ||
</> | ||
); | ||
}; | ||
|
||
describe('fltyte-api/ApiProvider', () => { | ||
it('getLoginUrl properly adds redirect url', () => { | ||
const result = getLoginUrl(AdminEndpoint.Version, `http://some.nonsense`); | ||
expect(result).toEqual('/version/login?redirect_url=http://some.nonsense'); | ||
}); | ||
|
||
it('If FlyteApiContext is not defined, returned URL uses default value', () => { | ||
const { getAllByText } = render(<MockCoponent />); | ||
expect(getAllByText('#').length).toBe(3); | ||
}); | ||
|
||
it('If FlyteApiContext is defined, but flyteApiDomain is not point to localhost', () => { | ||
const { getByText } = render( | ||
<FlyteApiProvider> | ||
<MockCoponent /> | ||
</FlyteApiProvider>, | ||
); | ||
expect(getByText('http://localhost/me')).toBeInTheDocument(); | ||
}); | ||
|
||
it('If FlyteApiContext provides flyteApiDomain value', () => { | ||
const { getByText } = render( | ||
<FlyteApiProvider flyteApiDomain="https://some.domain.here"> | ||
<MockCoponent /> | ||
</FlyteApiProvider>, | ||
); | ||
expect(getByText('https://some.domain.here/me')).toBeInTheDocument(); | ||
}); | ||
}); |
This file contains 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,56 @@ | ||
import * as React from 'react'; | ||
import { createContext, useContext } from 'react'; | ||
import { getAdminApiUrl, getEndpointUrl } from '../utils'; | ||
import { AdminEndpoint, RawEndpoint } from '../utils/constants'; | ||
import { defaultLoginStatus, getLoginUrl, LoginStatus } from './login'; | ||
|
||
export interface FlyteApiContextState { | ||
loginStatus: LoginStatus; | ||
getLoginUrl: (redirect?: string) => string; | ||
getProfileUrl: () => string; | ||
getAdminApiUrl: (endpoint: AdminEndpoint | string) => string; | ||
} | ||
|
||
const FlyteApiContext = createContext<FlyteApiContextState>({ | ||
// default values - used when Provider wrapper is not found | ||
loginStatus: defaultLoginStatus, | ||
getLoginUrl: () => '#', | ||
getProfileUrl: () => '#', | ||
getAdminApiUrl: () => '#', | ||
}); | ||
|
||
interface FlyteApiProviderProps { | ||
flyteApiDomain?: string; | ||
children?: React.ReactNode; | ||
} | ||
|
||
export const useFlyteApi = () => useContext(FlyteApiContext); | ||
|
||
export const FlyteApiProvider = (props: FlyteApiProviderProps) => { | ||
const { flyteApiDomain } = props; | ||
|
||
const [loginExpired, setLoginExpired] = React.useState(false); | ||
|
||
// Whenever we detect expired credentials, trigger a login redirect automatically | ||
React.useEffect(() => { | ||
if (loginExpired) { | ||
window.location.href = getLoginUrl(flyteApiDomain); | ||
} | ||
}, [loginExpired]); | ||
|
||
return ( | ||
<FlyteApiContext.Provider | ||
value={{ | ||
loginStatus: { | ||
expired: loginExpired, | ||
setExpired: setLoginExpired, | ||
}, | ||
getLoginUrl: (redirect) => getLoginUrl(flyteApiDomain, redirect), | ||
getProfileUrl: () => getEndpointUrl(RawEndpoint.Profile, flyteApiDomain), | ||
getAdminApiUrl: (endpoint) => getAdminApiUrl(endpoint, flyteApiDomain), | ||
}} | ||
> | ||
{props.children} | ||
</FlyteApiContext.Provider> | ||
); | ||
}; |
This file contains 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,22 @@ | ||
import { getEndpointUrl } from '../utils'; | ||
import { RawEndpoint } from '../utils/constants'; | ||
|
||
export interface LoginStatus { | ||
expired: boolean; | ||
setExpired(expired: boolean): void; | ||
} | ||
|
||
export const defaultLoginStatus: LoginStatus = { | ||
expired: true, | ||
setExpired: () => { | ||
/** Do nothing */ | ||
}, | ||
}; | ||
|
||
/** Constructs a url for redirecting to the Admin login endpoint and returning | ||
* to the current location after completing the flow. | ||
*/ | ||
export function getLoginUrl(adminUrl?: string, redirectUrl: string = window.location.href) { | ||
const baseUrl = getEndpointUrl(RawEndpoint.Login, adminUrl); | ||
return `${baseUrl}?redirect_url=${redirectUrl}`; | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains 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 |
---|---|---|
@@ -1 +1,4 @@ | ||
export { SampleComponent } from './Sample'; | ||
export { FlyteApiProvider, useFlyteApi, type FlyteApiContextState } from './ApiProvider'; | ||
|
||
export { AdminEndpoint, RawEndpoint } from './utils/constants'; | ||
export { getAxiosApiCall, defaultAxiosConfig } from './utils'; |
This file contains 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,10 @@ | ||
export enum RawEndpoint { | ||
Login = '/login', | ||
Profile = '/me', | ||
} | ||
|
||
export const adminApiPrefix = '/api/v1'; | ||
|
||
export enum AdminEndpoint { | ||
Version = '/version', | ||
} |
This file contains 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,36 @@ | ||
/* eslint-disable max-classes-per-file */ | ||
import { AxiosError } from 'axios'; | ||
|
||
export class NotFoundError extends Error { | ||
constructor(public override name: string, msg = 'The requested item could not be found') { | ||
super(msg); | ||
} | ||
} | ||
|
||
/** Indicates failure to fetch a resource because the user is not authorized (401) */ | ||
export class NotAuthorizedError extends Error { | ||
constructor(msg = 'User is not authorized to view this resource') { | ||
super(msg); | ||
} | ||
} | ||
|
||
/** Detects special cases for errors returned from Axios and lets others pass through. */ | ||
export function transformRequestError(err: unknown, path: string) { | ||
const error = err as AxiosError; | ||
|
||
if (!error.response) { | ||
return error; | ||
} | ||
|
||
// For some status codes, we'll throw a special error to allow | ||
// client code and components to handle separately | ||
if (error.response.status === 404) { | ||
return new NotFoundError(path); | ||
} | ||
if (error.response.status === 401) { | ||
return new NotAuthorizedError(); | ||
} | ||
|
||
// this error is not decoded. | ||
return error; | ||
} |
Oops, something went wrong.