Skip to content

Commit

Permalink
Merge pull request #34 from l3montree-dev/29-replace-personal-access-…
Browse files Browse the repository at this point in the history
…token-random-string-with-a-private-key

29 replace personal access token random string with a private key
  • Loading branch information
timbastin authored Jun 27, 2024
2 parents a57badf + 58033c3 commit ebdff93
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 33 deletions.
26 changes: 12 additions & 14 deletions src/components/risk-identification/SCADialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useActiveOrg } from "@/hooks/useActiveOrg";
import usePersonalAccessToken from "@/hooks/usePersonalAccessToken";
import { Tab } from "@headlessui/react";
import Image from "next/image";
import { Dispatch, FunctionComponent, SetStateAction, useEffect } from "react";
import { Dispatch, FunctionComponent, SetStateAction } from "react";

import CopyCode from "../common/CopyCode";
import CustomTab from "../common/CustomTab";
Expand All @@ -20,6 +20,7 @@ import {
} from "../ui/dialog";
import Steps from "./Steps";

import { PatWithPrivKey } from "@/types/api/api";
import { Button } from "../ui/button";
import {
Card,
Expand All @@ -40,6 +41,10 @@ const SCADialog: FunctionComponent<Props> = ({ open, setOpen }) => {
const activeOrg = useActiveOrg();
const { personalAccessTokens, onCreatePat } = usePersonalAccessToken();

const pat =
personalAccessTokens.length > 0
? (personalAccessTokens[0] as PatWithPrivKey)
: undefined;
return (
<Dialog open={open}>
<DialogContent setOpen={setOpen}>
Expand All @@ -63,15 +68,12 @@ const SCADialog: FunctionComponent<Props> = ({ open, setOpen }) => {
Token. You can create such a token by clicking the button below.
</CardDescription>
</CardHeader>
{personalAccessTokens.length > 0 && (
{pat && (
<CardContent>
<div className="flex flex-row items-center justify-between">
<div className="flex-1">
<div className="mb-2 flex flex-row gap-2">
<CopyCode
language="shell"
codeString={personalAccessTokens[0].token}
/>
<CopyCode language="shell" codeString={pat.privKey} />
</div>

<span className=" block text-right text-sm text-destructive">
Expand All @@ -82,7 +84,7 @@ const SCADialog: FunctionComponent<Props> = ({ open, setOpen }) => {
</div>
</CardContent>
)}
{personalAccessTokens.length === 0 && (
{!pat && (
<CardFooter>
<Button
variant={"default"}
Expand Down Expand Up @@ -178,11 +180,7 @@ const SCADialog: FunctionComponent<Props> = ({ open, setOpen }) => {
</span>
<CopyCode
language="shell"
codeString={
personalAccessTokens.length > 0
? personalAccessTokens[0].token
: "<PERSONAL ACCESS TOKEN>"
}
codeString={pat?.privKey ?? "<PERSONAL ACCESS TOKEN>"}
/>
</div>
</CardContent>
Expand Down Expand Up @@ -236,7 +234,7 @@ jobs:
codeString={`flawfind sca \\
--assetName="${activeOrg?.slug}/projects/${router.query.projectSlug}/assets/${router.query.assetSlug}" \\
--apiUrl="${config.flawFixApiUrl}" \\
--token="${personalAccessTokens.length > 0 ? personalAccessTokens[0].token : "<YOU NEED TO CREATE A PERSONAL ACCESS TOKEN>"}"`}
--token="${pat?.privKey ?? "<YOU NEED TO CREATE A PERSONAL ACCESS TOKEN>"}"`}
></CopyCode>
</Tab.Panel>
<Tab.Panel>
Expand All @@ -245,7 +243,7 @@ jobs:
codeString={`flawfind sca \\
--assetName="${activeOrg?.slug}/projects/${router.query.projectSlug}/assets/${router.query.assetSlug}" \\
--apiUrl="${config.flawFixApiUrl}" \\
--token="${personalAccessTokens.length > 0 ? personalAccessTokens[0].token : "<YOU NEED TO CREATE A PERSONAL ACCESS TOKEN>"}"`}
--token="${pat?.privKey ?? "<YOU NEED TO CREATE A PERSONAL ACCESS TOKEN>"}"`}
></CopyCode>
</Tab.Panel>
</Tab.Panels>
Expand Down
5 changes: 2 additions & 3 deletions src/hooks/usePersonalAccessToken.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { browserApiClient } from "@/services/flawFixApi";
import { createPat } from "@/services/patService";
import { PersonalAccessTokenDTO } from "@/types/api/api";
import { PatWithPrivKey, PersonalAccessTokenDTO } from "@/types/api/api";
import { useState } from "react";

export default function usePersonalAccessToken(
existingPats?: PersonalAccessTokenDTO[],
) {
const [personalAccessTokens, setPersonalAccessTokens] = useState<
Array<PersonalAccessTokenDTO & { token?: string }>
Array<PersonalAccessTokenDTO | PatWithPrivKey>
>(existingPats ?? []);
const handleDeletePat = async (pat: PersonalAccessTokenDTO) => {
await browserApiClient(`/pats/${pat.id}/`, {
Expand All @@ -20,7 +20,6 @@ export default function usePersonalAccessToken(

const handleCreatePat = async (data: { description: string }) => {
const pat = await createPat(data);

setPersonalAccessTokens([...personalAccessTokens, pat]);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const DependencyGraphPage: FunctionComponent<{
);
}}
>
<SelectTrigger>
<SelectTrigger className="bg-background">
<SelectValue
defaultValue={versions[0]}
placeholder={versions[0]}
Expand Down
18 changes: 8 additions & 10 deletions src/pages/user-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,12 @@ const Settings: FunctionComponent<{
>
<div className="mb-6 flex flex-col gap-4">
{personalAccessTokens.map((pat) =>
pat.token ? (
"privKey" in pat ? (
<>
<Card>
<CardContent className="pt-6">
<CopyCode
codeString={pat.token}
codeString={pat.privKey}
language="shell"
></CopyCode>
<span className="mt-2 block text-sm text-destructive">
Expand All @@ -288,14 +288,12 @@ const Settings: FunctionComponent<{
</>
}
Button={
!pat.token ? (
<Button
variant="destructive"
onClick={() => onDeletePat(pat)}
>
Delete
</Button>
) : undefined
<Button
variant="destructive"
onClick={() => onDeletePat(pat)}
>
Delete
</Button>
}
/>
),
Expand Down
78 changes: 78 additions & 0 deletions src/services/keyService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
export const generateKeyPair = async (): Promise<{
privateKey: string;
publicKey: string;
}> => {
let privateKey = "";
let publicKey = "";

// Generate an ECDSA P-256 key pair
const keyPair = await window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-256",
},
true, // extractable
["sign", "verify"],
);

// Export the private key in JWK format
const exportedPrivateKey = await window.crypto.subtle.exportKey(
"jwk",
keyPair.privateKey,
);

// Export the public key in JWK format
const exportedPublicKey = await window.crypto.subtle.exportKey(
"jwk",
keyPair.publicKey,
);

// Extract the raw private key bytes (base64url decode the 'd' field)
const rawPrivateKeyBytes = base64urlDecode(exportedPrivateKey.d as string);

// Convert to a hex string for easier reading (optional)
const hexPrivateKey = Array.from(rawPrivateKeyBytes, (byte) =>
byte.toString(16).padStart(2, "0"),
).join("");

// Extract the raw public key coordinates (base64url decode the 'x' and 'y' fields)
const rawPublicKeyXBytes = base64urlDecode(exportedPublicKey.x as string);
const rawPublicKeyYBytes = base64urlDecode(exportedPublicKey.y as string);

// Convert to hex strings for easier reading (optional)
const hexPublicKeyX = Array.from(rawPublicKeyXBytes, (byte) =>
byte.toString(16).padStart(2, "0"),
).join("");
const hexPublicKeyY = Array.from(rawPublicKeyYBytes, (byte) =>
byte.toString(16).padStart(2, "0"),
).join("");

// Combine x and y coordinates into a single string
const combinedPublicKey = hexPublicKeyX + hexPublicKeyY;

privateKey = hexPrivateKey;
publicKey = combinedPublicKey;

return {
privateKey,
publicKey,
};
};

// Function to decode base64url
function base64urlDecode(base64url: string) {
// Add padding if missing
while (base64url.length % 4) {
base64url += "=";
}
// Replace URL-safe characters
base64url = base64url.replace(/-/g, "+").replace(/_/g, "/");
// Decode Base64
const decodedString = atob(base64url);
// Convert to Uint8Array
const byteArray = new Uint8Array(decodedString.length);
for (let i = 0; i < decodedString.length; i++) {
byteArray[i] = decodedString.charCodeAt(i);
}
return byteArray;
}
21 changes: 17 additions & 4 deletions src/services/patService.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import { PersonalAccessTokenDTO } from "@/types/api/api";
import { PatWithPrivKey, PersonalAccessTokenDTO } from "@/types/api/api";
import { browserApiClient } from "./flawFixApi";
import { generateKeyPair } from "./keyService";

const createPat = async (data: {
description: string;
}): Promise<PatWithPrivKey> => {
// generate public private key pair
const { privateKey, publicKey } = await generateKeyPair();

const d = { ...data, pubKey: publicKey };

const createPat = async (data: { description: string }) => {
const pat: PersonalAccessTokenDTO = await (
await browserApiClient("/pats/", {
method: "POST",
body: JSON.stringify(data),
// send hex-encoded pubkey
body: JSON.stringify(d),
})
).json();
return pat;

return {
...pat,
privKey: privateKey,
};
};

export { createPat };
7 changes: 6 additions & 1 deletion src/types/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ export interface PersonalAccessTokenDTO {
userId: string;
createdAt: string;
id: string;
token: string;
pubKey: string;
fingerprint: string;
}

export interface PatWithPrivKey extends PersonalAccessTokenDTO {
privKey: string;
}

export interface ProjectDTO {
Expand Down

0 comments on commit ebdff93

Please sign in to comment.