-
Notifications
You must be signed in to change notification settings - Fork 90
feat(fronted): tenanted admin api credentials #3213
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
Changes from all commits
c8fd314
6bc5f5a
f6ef572
9bd0f5e
8d06621
a319ca0
2b47182
659ceb6
4fffdac
4d0232d
0f6ef8b
74e0680
96f2821
6d2be5f
4d8b98d
5645a83
64f1db0
85e7353
66f6199
092609e
16a774f
67e43c3
ffaacff
6ba17da
8bf1580
7896509
25dd789
f5cfa09
bb4c694
b82bec0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { Form, useActionData, useNavigation } from '@remix-run/react' | ||
import { useRef, useState, useEffect } from 'react' | ||
import { Input, Button } from '~/components/ui' | ||
import { validate as validateUUID } from 'uuid' | ||
|
||
interface ApiCredentialsFormProps { | ||
showClearCredentials: boolean | ||
defaultTenantId: string | ||
defaultApiSecret: string | ||
} | ||
|
||
interface ActionErrorResponse { | ||
status: number | ||
statusText: string | ||
} | ||
|
||
export const ApiCredentialsForm = ({ | ||
showClearCredentials, | ||
defaultTenantId, | ||
defaultApiSecret | ||
}: ApiCredentialsFormProps) => { | ||
const actionData = useActionData<ActionErrorResponse>() | ||
const navigation = useNavigation() | ||
const inputRef = useRef<HTMLInputElement>(null) | ||
const formRef = useRef<HTMLFormElement>(null) | ||
const [tenantIdError, setTenantIdError] = useState<string | null>(null) | ||
|
||
const isSubmitting = navigation.state === 'submitting' | ||
|
||
const handleTenantIdChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const tenantId = event.target.value.trim() | ||
|
||
if (tenantId === '') { | ||
setTenantIdError('Tenant ID is required') | ||
} else if (!validateUUID(tenantId)) { | ||
setTenantIdError('Invalid Tenant ID (must be a valid UUID)') | ||
} else { | ||
setTenantIdError(null) | ||
} | ||
} | ||
|
||
// auto submit form if values passed in | ||
useEffect(() => { | ||
if (defaultTenantId && defaultApiSecret && !tenantIdError) { | ||
if (formRef.current) { | ||
formRef.current.submit() | ||
} | ||
} | ||
}, [defaultTenantId, defaultApiSecret, tenantIdError]) | ||
|
||
return ( | ||
<div className='space-y-4'> | ||
{showClearCredentials ? ( | ||
<Form method='post' action='/api/set-credentials' className='space-y-4'> | ||
<p className='text-green-600'>✓ API credentials configured</p> | ||
<input hidden readOnly name='intent' value='clear' /> | ||
<Button | ||
type='submit' | ||
intent='danger' | ||
aria-label='Clear API credentials' | ||
disabled={isSubmitting} | ||
> | ||
{isSubmitting ? 'Submitting...' : 'Clear Credentials'} | ||
</Button> | ||
</Form> | ||
) : ( | ||
<Form | ||
method='post' | ||
action='/api/set-credentials' | ||
className='space-y-4' | ||
ref={formRef} // Reference for the credentials form | ||
> | ||
<Input | ||
ref={inputRef} | ||
required | ||
type='text' | ||
name='tenantId' | ||
label='Tenant ID' | ||
defaultValue={defaultTenantId} | ||
onChange={handleTenantIdChange} | ||
aria-invalid={!!tenantIdError} | ||
aria-describedby={tenantIdError ? 'tenantId-error' : undefined} | ||
/> | ||
{tenantIdError && ( | ||
<p id='tenantId-error' className='text-red-500 text-sm'> | ||
{tenantIdError} | ||
</p> | ||
)} | ||
<Input | ||
required | ||
type='password' | ||
name='apiSecret' | ||
label='API Secret' | ||
defaultValue={defaultApiSecret} | ||
/> | ||
<input hidden readOnly name='intent' value='save' /> | ||
<div className='flex justify-center'> | ||
<Button | ||
type='submit' | ||
aria-label='Save API credentials' | ||
disabled={!!tenantIdError || isSubmitting} | ||
> | ||
{isSubmitting ? 'Submitting...' : 'Save Credentials'} | ||
</Button> | ||
</div> | ||
</Form> | ||
)} | ||
{actionData?.statusText && ( | ||
<div className='text-red-500'>{actionData.statusText}</div> | ||
)} | ||
</div> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,9 +27,10 @@ import type { | |
WithdrawAssetLiquidity, | ||
WithdrawAssetLiquidityVariables | ||
} from '~/generated/graphql' | ||
import { apolloClient } from '../apollo.server' | ||
import { getApolloClient } from '../apollo.server' | ||
|
||
export const getAssetInfo = async (args: QueryAssetArgs) => { | ||
export const getAssetInfo = async (request: Request, args: QueryAssetArgs) => { | ||
const apolloClient = await getApolloClient(request) | ||
Comment on lines
+32
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. example of changs to apollo client to support requestor specific api signature and tenant id header as described here: https://github.com/interledger/rafiki/pull/3213/files#r1916826388 The gql requests exported by these |
||
const response = await apolloClient.query< | ||
GetAssetQuery, | ||
GetAssetQueryVariables | ||
|
@@ -56,7 +57,11 @@ export const getAssetInfo = async (args: QueryAssetArgs) => { | |
return response.data.asset | ||
} | ||
|
||
export const getAssetWithFees = async (args: QueryAssetArgs) => { | ||
export const getAssetWithFees = async ( | ||
request: Request, | ||
args: QueryAssetArgs | ||
) => { | ||
const apolloClient = await getApolloClient(request) | ||
const response = await apolloClient.query< | ||
GetAssetWithFeesQuery, | ||
GetAssetWithFeesQueryVariables | ||
|
@@ -97,7 +102,8 @@ export const getAssetWithFees = async (args: QueryAssetArgs) => { | |
return response.data.asset | ||
} | ||
|
||
export const listAssets = async (args: QueryAssetsArgs) => { | ||
export const listAssets = async (request: Request, args: QueryAssetsArgs) => { | ||
const apolloClient = await getApolloClient(request) | ||
const response = await apolloClient.query< | ||
ListAssetsQuery, | ||
ListAssetsQueryVariables | ||
|
@@ -130,11 +136,11 @@ export const listAssets = async (args: QueryAssetsArgs) => { | |
`, | ||
variables: args | ||
}) | ||
|
||
return response.data.assets | ||
} | ||
|
||
export const createAsset = async (args: CreateAssetInput) => { | ||
export const createAsset = async (request: Request, args: CreateAssetInput) => { | ||
const apolloClient = await getApolloClient(request) | ||
const response = await apolloClient.mutate< | ||
CreateAssetMutation, | ||
CreateAssetMutationVariables | ||
|
@@ -166,7 +172,8 @@ export const createAsset = async (args: CreateAssetInput) => { | |
return response.data?.createAsset | ||
} | ||
|
||
export const updateAsset = async (args: UpdateAssetInput) => { | ||
export const updateAsset = async (request: Request, args: UpdateAssetInput) => { | ||
const apolloClient = await getApolloClient(request) | ||
const response = await apolloClient.mutate< | ||
UpdateAssetMutation, | ||
UpdateAssetMutationVariables | ||
|
@@ -198,7 +205,8 @@ export const updateAsset = async (args: UpdateAssetInput) => { | |
return response.data?.updateAsset | ||
} | ||
|
||
export const setFee = async (args: SetFeeInput) => { | ||
export const setFee = async (request: Request, args: SetFeeInput) => { | ||
const apolloClient = await getApolloClient(request) | ||
const response = await apolloClient.mutate< | ||
SetFeeMutation, | ||
SetFeeMutationVariables | ||
|
@@ -226,8 +234,10 @@ export const setFee = async (args: SetFeeInput) => { | |
} | ||
|
||
export const depositAssetLiquidity = async ( | ||
request: Request, | ||
args: DepositAssetLiquidityInput | ||
) => { | ||
const apolloClient = await getApolloClient(request) | ||
const response = await apolloClient.mutate< | ||
DepositAssetLiquidityMutation, | ||
DepositAssetLiquidityMutationVariables | ||
|
@@ -250,8 +260,10 @@ export const depositAssetLiquidity = async ( | |
} | ||
|
||
export const withdrawAssetLiquidity = async ( | ||
request: Request, | ||
args: CreateAssetLiquidityWithdrawalInput | ||
) => { | ||
const apolloClient = await getApolloClient(request) | ||
const response = await apolloClient.mutate< | ||
WithdrawAssetLiquidity, | ||
WithdrawAssetLiquidityVariables | ||
|
@@ -273,13 +285,13 @@ export const withdrawAssetLiquidity = async ( | |
return response.data?.createAssetLiquidityWithdrawal | ||
} | ||
|
||
export const loadAssets = async () => { | ||
export const loadAssets = async (request: Request) => { | ||
let assets: ListAssetsQuery['assets']['edges'] = [] | ||
let hasNextPage = true | ||
let after: string | undefined | ||
|
||
while (hasNextPage) { | ||
const response = await listAssets({ first: 100, after }) | ||
const response = await listAssets(request, { first: 100, after }) | ||
|
||
if (!response.edges.length) { | ||
return [] | ||
|
@@ -295,7 +307,8 @@ export const loadAssets = async () => { | |
return assets | ||
} | ||
|
||
export const deleteAsset = async (args: DeleteAssetInput) => { | ||
export const deleteAsset = async (request: Request, args: DeleteAssetInput) => { | ||
const apolloClient = await getApolloClient(request) | ||
const response = await apolloClient.mutate< | ||
DeleteAssetMutation, | ||
DeleteAssetMutationVariables | ||
|
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.
It doesn't seem like
SIGNATURE_SECRET
is set in the environment anymore.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.
It was removed from the frontend - is that what you're referring to? The docker compose sets them for the mock ase. When I spin up rafiki it doesnt error on the lines below for the secret, and it spits out a link with the
apiSecret
.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.
Ah, got it. My mistake.