Skip to content
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

feat(api-keys): add read_only field to api-keys #3554

Merged
merged 8 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/dashboard/app/api-keys/server-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const revokeApiKeyServerAction = async (id: string) => {

export const createApiKeyServerAction = async (_prevState: unknown, form: FormData) => {
const apiKeyName = form.get("apiKeyName")
const readOnly = form.get("apiScope") === "readOnly"

if (!apiKeyName || typeof apiKeyName !== "string") {
return {
error: true,
Expand All @@ -72,7 +74,7 @@ export const createApiKeyServerAction = async (_prevState: unknown, form: FormDa

let data
try {
data = await createApiKey(token, apiKeyName, apiKeyExpiresInDays)
data = await createApiKey(token, apiKeyName, apiKeyExpiresInDays, readOnly)
} catch (err) {
console.log("error in createApiKey ", err)
return {
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const authOptions: AuthOptions = {
clientSecret: env.CLIENT_SECRET,
wellKnown: `${env.HYDRA_PUBLIC}/.well-known/openid-configuration`,
authorization: {
params: { scope: "offline transactions:read payments:send" },
params: { scope: "read write" },
},
idToken: false,
name: "Blink",
Expand Down
22 changes: 14 additions & 8 deletions apps/dashboard/components/api-keys/api-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ import React from "react"
import { Card, Divider, Typography, Box } from "@mui/joy"

import RevokeKey from "./revoke"
import { formatDate } from "./utils"
import { formatDate, getScopeText } from "./utils"

interface ApiKey {
id: string
name: string
createdAt: number
expiresAt: number
lastUsedAt?: number | null | undefined
expired: boolean
revoked: boolean
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
readonly readOnly: boolean
}

interface ApiKeysCardProps {
Expand Down Expand Up @@ -47,6 +49,10 @@ const ApiKeysCard: React.FC<ApiKeysCardProps> = ({
<Typography fontSize={13}>Expires At</Typography>
<Typography fontSize={13}>{formatDate(key.expiresAt)}</Typography>
</Box>
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Typography fontSize={13}>Scope</Typography>
<Typography fontSize={13}>{getScopeText(key.readOnly)}</Typography>
</Box>
{!key.revoked && !key.expired && <RevokeKey id={key.id} />}
</Card>
))
Expand Down
14 changes: 13 additions & 1 deletion apps/dashboard/components/api-keys/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
Tooltip,
Select,
Option,
Radio,
RadioGroup,
} from "@mui/joy"

import InfoOutlined from "@mui/icons-material/InfoOutlined"
Expand Down Expand Up @@ -254,7 +256,17 @@ const ApiKeyCreate = () => {
{state.message}
</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>
</Box>
<Box
sx={{
display: "flex",
Expand Down
89 changes: 49 additions & 40 deletions apps/dashboard/components/api-keys/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import Typography from "@mui/joy/Typography"
import Divider from "@mui/joy/Divider"

import RevokeKey from "./revoke"
import { formatDate } from "./utils"
import { formatDate, getScopeText } from "./utils"

interface ApiKey {
id: string
name: string
createdAt: number
expiresAt: number
lastUsedAt?: number | null
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
readonly readOnly: boolean
}

interface ApiKeysListProps {
Expand All @@ -32,26 +36,28 @@ const ApiKeysList: React.FC<ApiKeysListProps> = ({
<thead>
<tr>
<th style={{ width: "20%" }}>Name</th>
<th style={{ width: "30%" }}>API Key ID</th>
<th style={{ width: "20%" }}>Expires At</th>
<th style={{ width: "20%" }}>Last Used</th>
<th style={{ width: "10%", textAlign: "right" }}>Action</th>
<th style={{ width: "25%" }}>API Key ID</th>
<th style={{ width: "15%" }}>Scope</th>
<th style={{ width: "15%" }}>Expires At</th>
<th style={{ width: "15%" }}>Last Used</th>
<th style={{ width: "5%", textAlign: "right" }}>Action</th>
</tr>
</thead>
<tbody>
{activeKeys.map(({ id, name, expiresAt, lastUsedAt }) => (
<tr key={id}>
<td style={{ width: "20%" }}>{name}</td>
<td style={{ width: "30%" }}>{id}</td>
<td style={{ width: "20%" }}>{formatDate(expiresAt)}</td>
<td style={{ width: "20%" }}>
{lastUsedAt ? formatDate(lastUsedAt) : "Never"}
</td>
<td style={{ width: "10%", textAlign: "right" }}>
<RevokeKey id={id} />
</td>
</tr>
))}
{activeKeys.map(({ id, name, expiresAt, lastUsedAt, readOnly }) => {
return (
<tr key={id}>
<td>{name}</td>
<td>{id}</td>
<td>{getScopeText(readOnly)}</td>
<td>{formatDate(expiresAt)}</td>
<td>{lastUsedAt ? formatDate(lastUsedAt) : "Never"}</td>
<td style={{ textAlign: "right" }}>
<RevokeKey id={id} />
</td>
</tr>
)
})}
</tbody>
</Table>
{activeKeys.length === 0 && <Typography>No active keys to display.</Typography>}
Expand All @@ -62,19 +68,21 @@ const ApiKeysList: React.FC<ApiKeysListProps> = ({
<Table aria-label="revoked keys table">
<thead>
<tr>
<th style={{ width: "25%" }}>Name</th>
<th style={{ width: "35%" }}>API Key ID</th>
<th style={{ width: "15%" }}>Created At</th>
<th style={{ width: "20%" }}>Name</th>
<th style={{ width: "25%" }}>API Key ID</th>
<th style={{ width: "15%" }}>Scope</th>
<th style={{ width: "20%" }}>Created At</th>
<th style={{ textAlign: "right", width: "15%" }}>Status</th>
</tr>
</thead>
<tbody>
{revokedKeys.map(({ id, name, createdAt }) => (
{revokedKeys.map(({ id, name, createdAt, readOnly }) => (
<tr key={id}>
<td style={{ width: "25%" }}>{name}</td>
<td style={{ width: "35%" }}>{id}</td>
<td style={{ width: "15%" }}>{formatDate(createdAt)}</td>
<td style={{ textAlign: "right", width: "15%" }}>Revoked</td>
<td>{name}</td>
<td>{id}</td>
<td>{getScopeText(readOnly)}</td>
<td>{formatDate(createdAt)}</td>
<td style={{ textAlign: "right" }}>Revoked</td>
</tr>
))}
</tbody>
Expand All @@ -83,25 +91,26 @@ const ApiKeysList: React.FC<ApiKeysListProps> = ({

<Divider />

{/* Expired Keys Section */}
<Typography fontSize={22}>Expired Keys</Typography>
<Table aria-label="expired keys table">
<thead>
<tr>
<th style={{ width: "25%" }}>Name</th>
<th style={{ width: "35%" }}>API Key ID</th>
<th style={{ width: "20%" }}>Name</th>
<th style={{ width: "25%" }}>API Key ID</th>
<th style={{ width: "15%" }}>Scope</th>
<th style={{ width: "20%" }}>Created At</th>
<th style={{ textAlign: "right", width: "15%" }}>Expires At</th>
</tr>
</thead>
<tbody>
{expiredKeys.map(({ id, name, createdAt, expiresAt }) => (
{expiredKeys.map(({ id, name, createdAt, expiresAt, readOnly }) => (
<tr key={id}>
<td style={{ width: "25%" }}>{name}</td>
<td style={{ width: "35%" }}>{id}</td>
<td style={{ width: "20%" }}>{formatDate(createdAt)}</td>
<td style={{ textAlign: "right", width: "15%" }}>
{formatDate(expiresAt)}
</td>
<td>{name}</td>
<td>{id}</td>
<td>{getScopeText(readOnly)}</td>
<td>{formatDate(createdAt)}</td>
<td style={{ textAlign: "right" }}>{formatDate(expiresAt)}</td>
</tr>
))}
</tbody>
Expand Down
4 changes: 4 additions & 0 deletions apps/dashboard/components/api-keys/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ 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"
}
Loading
Loading