Skip to content

Commit

Permalink
feat(dashboard): made changes in dashboard for scopes
Browse files Browse the repository at this point in the history
  • Loading branch information
siddhart1o1 committed Feb 22, 2024
1 parent 1de1a0a commit b477a8c
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 29 deletions.
17 changes: 14 additions & 3 deletions apps/dashboard/app/api-keys/server-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,20 @@ export const createApiKeyServerAction = async (
): Promise<ApiKeyResponse> => {
let apiKeyExpiresInDays: number | null = null
const apiKeyName = form.get("apiKeyName")
const readOnly = form.get("apiScope") === "readOnly"
const apiKeyExpiresInDaysSelect = form.get("apiKeyExpiresInDaysSelect")
const scopes = []
if (form.get("readScope")) scopes.push("READ")
if (form.get("receiveScope")) scopes.push("RECEIVE")
if (form.get("writeScope")) scopes.push("WRITE")

if (scopes.length === 0) {
return {
error: true,
message: "At least one scope is required",
responsePayload: null,
}
}

const apiKeyExpiresInDaysSelect = form.get("apiKeyExpiresInDaysSelect")
if (!apiKeyName || typeof apiKeyName !== "string") {
return {
error: true,
Expand Down Expand Up @@ -85,7 +96,7 @@ export const createApiKeyServerAction = async (

let data
try {
data = await createApiKey(token, apiKeyName, apiKeyExpiresInDays, readOnly)
data = await createApiKey(token, apiKeyName, apiKeyExpiresInDays, scopes)
} catch (err) {
console.log("error in createApiKey ", err)
return {
Expand Down
3 changes: 2 additions & 1 deletion apps/dashboard/components/api-keys/api-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface ApiKey {
readonly lastUsedAt?: number | null
readonly expiresAt?: number | null
readonly readOnly: boolean
readonly scopes: string[]
}

interface ApiKeysCardProps {
Expand Down Expand Up @@ -53,7 +54,7 @@ const ApiKeysCard: React.FC<ApiKeysCardProps> = ({
</Box>
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Typography fontSize={13}>Scope</Typography>
<Typography fontSize={13}>{getScopeText(key.readOnly)}</Typography>
<Typography fontSize={13}>{getScopeText(key.scopes)}</Typography>
</Box>
{!key.revoked && !key.expired && <RevokeKey id={key.id} />}
</Card>
Expand Down
41 changes: 30 additions & 11 deletions apps/dashboard/components/api-keys/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import {
Tooltip,
Select,
Option,
Radio,
RadioGroup,
Checkbox,
} from "@mui/joy"

import InfoOutlined from "@mui/icons-material/InfoOutlined"
Expand Down Expand Up @@ -301,15 +300,35 @@ const ApiKeyCreate = ({ defaultWalletId }: Prop) => {
</FormHelperText>
) : null}
<Box>
<Typography>Scope</Typography>
<RadioGroup defaultValue="readAndWrite" name="apiScope">
<Radio value="readAndWrite" label="Read and Write" />
<FormHelperText>
Full access: read and write account details.
</FormHelperText>
<Radio value="readOnly" label="Read Only" />
<FormHelperText>Limited access: view data only.</FormHelperText>
</RadioGroup>
<Typography>Scopes</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "0.8em",
marginTop: "1em",
}}
>
<Checkbox
data-testid="read-scope-checkbox"
name="readScope"
id="readScope"
label="Read"
value="READ"
/>
<Checkbox
name="receiveScope"
id="receiveScope"
label="Receive"
value="RECEIVE"
/>
<Checkbox
name="writeScope"
id="writeScope"
label="Write"
value="WRITE"
/>
</Box>
</Box>
<Box
sx={{
Expand Down
13 changes: 7 additions & 6 deletions apps/dashboard/components/api-keys/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface ApiKey {
readonly lastUsedAt?: number | null
readonly expiresAt?: number | null
readonly readOnly: boolean
readonly scopes: string[]
}

interface ApiKeysListProps {
Expand Down Expand Up @@ -44,12 +45,12 @@ const ApiKeysList: React.FC<ApiKeysListProps> = ({
</tr>
</thead>
<tbody>
{activeKeys.map(({ id, name, expiresAt, lastUsedAt, readOnly }) => {
{activeKeys.map(({ id, name, expiresAt, lastUsedAt, scopes }) => {
return (
<tr key={id}>
<td>{name}</td>
<td>{id}</td>
<td>{getScopeText(readOnly)}</td>
<td>{getScopeText(scopes)}</td>
<td>{expiresAt ? formatDate(expiresAt) : "Never"}</td>
<td>{lastUsedAt ? formatDate(lastUsedAt) : "Never"}</td>
<td style={{ textAlign: "right" }}>
Expand All @@ -76,11 +77,11 @@ const ApiKeysList: React.FC<ApiKeysListProps> = ({
</tr>
</thead>
<tbody>
{revokedKeys.map(({ id, name, createdAt, readOnly }) => (
{revokedKeys.map(({ id, name, createdAt, scopes }) => (
<tr key={id}>
<td>{name}</td>
<td>{id}</td>
<td>{getScopeText(readOnly)}</td>
<td>{getScopeText(scopes)}</td>
<td>{formatDate(createdAt)}</td>
<td style={{ textAlign: "right" }}>Revoked</td>
</tr>
Expand All @@ -104,11 +105,11 @@ const ApiKeysList: React.FC<ApiKeysListProps> = ({
</tr>
</thead>
<tbody>
{expiredKeys.map(({ id, name, createdAt, expiresAt, readOnly }) => (
{expiredKeys.map(({ id, name, createdAt, expiresAt, scopes }) => (
<tr key={id}>
<td>{name}</td>
<td>{id}</td>
<td>{getScopeText(readOnly)}</td>
<td>{getScopeText(scopes)}</td>
<td>{formatDate(createdAt)}</td>
<td style={{ textAlign: "right" }}>
{expiresAt ? formatDate(expiresAt) : "Never"}
Expand Down
8 changes: 6 additions & 2 deletions apps/dashboard/components/api-keys/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export const formatDate = (timestamp: number): string => {
return new Date(timestamp * 1000).toLocaleDateString(undefined, options)
}

export const getScopeText = (readOnly: boolean): string => {
return readOnly ? "Read Only" : "Read and Write"
export const getScopeText = (scopes: string[]): string => {
if (scopes.length > 0) {
return scopes.join(", ")
} else {
return "No Scopes Defined"
}
}
5 changes: 5 additions & 0 deletions apps/dashboard/cypress/e2e/api-keys/api-keys.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ describe("Callback Test", () => {
cy.get('[data-testid="create-api-expire-30-days-select"]').should("not.be.disabled")
cy.get('[data-testid="create-api-expire-30-days-select"]').click()

cy.get('[data-testid="read-scope-checkbox"]').should("exist")
cy.get('[data-testid="read-scope-checkbox"]').should("be.visible")
cy.get('[data-testid="read-scope-checkbox"]').should("not.be.disabled")
cy.get('[data-testid="read-scope-checkbox"]').click()

cy.get('[data-testid="create-api-create-btn"]').should("exist")
cy.get('[data-testid="create-api-create-btn"]').should("be.visible")
cy.get('[data-testid="create-api-create-btn"]').should("not.be.disabled")
Expand Down
21 changes: 17 additions & 4 deletions apps/dashboard/services/graphql/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,13 @@ export type ApiKey = {
readonly name: Scalars['String']['output'];
readonly readOnly: Scalars['Boolean']['output'];
readonly revoked: Scalars['Boolean']['output'];
readonly scopes: ReadonlyArray<Scope>;
};

export type ApiKeyCreateInput = {
readonly expireInDays?: InputMaybe<Scalars['Int']['input']>;
readonly name: Scalars['String']['input'];
readonly readOnly?: Scalars['Boolean']['input'];
readonly scopes?: ReadonlyArray<Scope>;
};

export type ApiKeyCreatePayload = {
Expand Down Expand Up @@ -1671,6 +1672,13 @@ export type SatAmountPayload = {
readonly errors: ReadonlyArray<Error>;
};

export const Scope = {
Read: 'READ',
Receive: 'RECEIVE',
Write: 'WRITE'
} as const;

export type Scope = typeof Scope[keyof typeof Scope];
export type SettlementVia = SettlementViaIntraLedger | SettlementViaLn | SettlementViaOnChain;

export type SettlementViaIntraLedger = {
Expand Down Expand Up @@ -2253,14 +2261,14 @@ export type ApiKeyCreateMutationVariables = Exact<{
}>;


export type ApiKeyCreateMutation = { readonly __typename: 'Mutation', readonly apiKeyCreate: { readonly __typename: 'ApiKeyCreatePayload', readonly apiKeySecret: string, readonly apiKey: { readonly __typename: 'ApiKey', readonly id: string, readonly name: string, readonly createdAt: number, readonly revoked: boolean, readonly expired: boolean, readonly lastUsedAt?: number | null, readonly expiresAt?: number | null } } };
export type ApiKeyCreateMutation = { readonly __typename: 'Mutation', readonly apiKeyCreate: { readonly __typename: 'ApiKeyCreatePayload', readonly apiKeySecret: string, readonly apiKey: { readonly __typename: 'ApiKey', readonly id: string, readonly name: string, readonly createdAt: number, readonly revoked: boolean, readonly expired: boolean, readonly lastUsedAt?: number | null, readonly expiresAt?: number | null, readonly scopes: ReadonlyArray<Scope> } } };

export type ApiKeyRevokeMutationVariables = Exact<{
input: ApiKeyRevokeInput;
}>;


export type ApiKeyRevokeMutation = { readonly __typename: 'Mutation', readonly apiKeyRevoke: { readonly __typename: 'ApiKeyRevokePayload', readonly apiKey: { readonly __typename: 'ApiKey', readonly id: string, readonly name: string, readonly createdAt: number, readonly revoked: boolean, readonly expired: boolean, readonly lastUsedAt?: number | null, readonly expiresAt?: number | null } } };
export type ApiKeyRevokeMutation = { readonly __typename: 'Mutation', readonly apiKeyRevoke: { readonly __typename: 'ApiKeyRevokePayload', readonly apiKey: { readonly __typename: 'ApiKey', readonly id: string, readonly name: string, readonly createdAt: number, readonly revoked: boolean, readonly expired: boolean, readonly lastUsedAt?: number | null, readonly expiresAt?: number | null, readonly scopes: ReadonlyArray<Scope> } } };

export type CallbackEndpointAddMutationVariables = Exact<{
input: CallbackEndpointAddInput;
Expand Down Expand Up @@ -2329,7 +2337,7 @@ export type UserTotpRegistrationValidateMutation = { readonly __typename: 'Mutat
export type ApiKeysQueryVariables = Exact<{ [key: string]: never; }>;


export type ApiKeysQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly apiKeys: ReadonlyArray<{ readonly __typename: 'ApiKey', readonly id: string, readonly name: string, readonly createdAt: number, readonly revoked: boolean, readonly expired: boolean, readonly lastUsedAt?: number | null, readonly expiresAt?: number | null, readonly readOnly: boolean }> } | null };
export type ApiKeysQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly apiKeys: ReadonlyArray<{ readonly __typename: 'ApiKey', readonly id: string, readonly name: string, readonly createdAt: number, readonly revoked: boolean, readonly expired: boolean, readonly lastUsedAt?: number | null, readonly expiresAt?: number | null, readonly readOnly: boolean, readonly scopes: ReadonlyArray<Scope> }> } | null };

export type CallbackEndpointsQueryVariables = Exact<{ [key: string]: never; }>;

Expand Down Expand Up @@ -2383,6 +2391,7 @@ export const ApiKeyCreateDocument = gql`
expired
lastUsedAt
expiresAt
scopes
}
apiKeySecret
}
Expand Down Expand Up @@ -2425,6 +2434,7 @@ export const ApiKeyRevokeDocument = gql`
expired
lastUsedAt
expiresAt
scopes
}
}
}
Expand Down Expand Up @@ -2837,6 +2847,7 @@ export const ApiKeysDocument = gql`
lastUsedAt
expiresAt
readOnly
scopes
}
}
}
Expand Down Expand Up @@ -3511,6 +3522,7 @@ export type ResolversTypes = {
SafeInt: ResolverTypeWrapper<Scalars['SafeInt']['output']>;
SatAmount: ResolverTypeWrapper<Scalars['SatAmount']['output']>;
SatAmountPayload: ResolverTypeWrapper<SatAmountPayload>;
Scope: Scope;
Seconds: ResolverTypeWrapper<Scalars['Seconds']['output']>;
SettlementVia: ResolverTypeWrapper<ResolversUnionTypes<ResolversTypes>['SettlementVia']>;
SettlementViaIntraLedger: ResolverTypeWrapper<SettlementViaIntraLedger>;
Expand Down Expand Up @@ -3908,6 +3920,7 @@ export type ApiKeyResolvers<ContextType = any, ParentType extends ResolversParen
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
readOnly?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
revoked?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
scopes?: Resolver<ReadonlyArray<ResolversTypes['Scope']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

Expand Down
6 changes: 4 additions & 2 deletions apps/dashboard/services/graphql/mutations/api-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ gql`
expired
lastUsedAt
expiresAt
scopes
}
apiKeySecret
}
Expand All @@ -34,6 +35,7 @@ gql`
expired
lastUsedAt
expiresAt
scopes
}
}
}
Expand All @@ -43,13 +45,13 @@ export async function createApiKey(
token: string,
name: string,
expireInDays: number | null,
readOnly: boolean,
scopes: string[],
) {
const client = apollo(token).getClient()
try {
const { data } = await client.mutate<ApiKeyCreateMutation>({
mutation: ApiKeyCreateDocument,
variables: { input: { name, expireInDays, readOnly } },
variables: { input: { name, expireInDays, scopes } },
})
return data
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/services/graphql/queries/api-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ gql`
lastUsedAt
expiresAt
readOnly
scopes
}
}
}
Expand Down

0 comments on commit b477a8c

Please sign in to comment.