Skip to content

Commit

Permalink
Update documentation and code parity
Browse files Browse the repository at this point in the history
  • Loading branch information
AnthonyRonning committed Feb 26, 2025
1 parent 052c493 commit 046f849
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 23 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ dist-ssr
# Environment files
.env
.env.local

# ai
*.pbmd
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ function App() {
return (
<OpenSecretDeveloper
apiUrl="https://developer.opensecret.cloud"
pcrConfig={{}} // Optional PCR configuration for attestation validation
>
<YourApp />
</OpenSecretDeveloper>
Expand All @@ -204,6 +205,7 @@ function DeveloperLogin() {
const response = await dev.signIn("[email protected]", "yourpassword");
console.log("Login successful", response);
// Now you can use the developer context APIs
// Authentication state is automatically updated
} catch (error) {
console.error("Login failed:", error);
}
Expand All @@ -219,15 +221,20 @@ function DeveloperLogin() {
);
console.log("Registration successful", response);
// Now you can use the developer context APIs
// Authentication state is automatically updated
} catch (error) {
console.error("Registration failed:", error);
}
}

// Sign out
function handleLogout() {
dev.signOut();
// The developer context will update automatically
async function handleLogout() {
try {
await dev.signOut();
// The developer context will update automatically
} catch (error) {
console.error("Logout failed:", error);
}
}

return (
Expand Down Expand Up @@ -278,6 +285,27 @@ function PlatformManagement() {
- `email`: Developer's email address
- `name`: Developer's name (optional)
- `organizations`: Array of organizations the developer belongs to
- `apiUrl`: The current OpenSecret developer API URL being used

#### Developer Authentication

- `signIn(email: string, password: string): Promise<PlatformLoginResponse>`: Signs in a developer with the provided email and password. Returns a response containing access and refresh tokens. The authentication state is automatically updated.
- `signUp(email: string, password: string, name?: string): Promise<PlatformLoginResponse>`: Registers a new developer account with the provided email, password, and optional name. Returns a response containing access and refresh tokens. The authentication state is automatically updated.
- `signOut(): Promise<void>`: Signs out the current developer by removing authentication tokens and making a server logout call.
- `refetchDeveloper(): Promise<void>`: Refreshes the developer's authentication state. Useful after making changes that affect developer profile or organization membership.

#### Attestation Verification

- `pcrConfig`: An object containing additional PCR0 hashes to validate against.
- `getAttestation`: Gets attestation from the enclave.
- `authenticate`: Authenticates an attestation document.
- `parseAttestationForView`: Parses an attestation document for viewing.
- `awsRootCertDer`: AWS root certificate in DER format.
- `expectedRootCertHash`: Expected hash of the AWS root certificate.
- `getAttestationDocument()`: Gets and verifies an attestation document from the enclave. This is a convenience function that:
1. Fetches the attestation document with a random nonce
2. Authenticates the document
3. Parses it for viewing

#### Organization Management

Expand Down
181 changes: 171 additions & 10 deletions src/lib/developer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import React, { createContext, useState, useEffect } from "react";
import * as platformApi from "./platformApi";
import { setPlatformApiUrl } from "./platformApi";
import { getAttestation } from "./getAttestation";
import { authenticate } from "./attestation";
import {
parseAttestationForView,
AWS_ROOT_CERT_DER,
EXPECTED_ROOT_CERT_HASH,
ParsedAttestationView
} from "./attestationForView";
import type { AttestationDocument } from "./attestation";
import { PcrConfig } from "./pcr";
import type {
Organization,
Project,
Expand Down Expand Up @@ -36,6 +46,12 @@ export type OpenSecretDeveloperContextType = {
* @param email - Developer's email address
* @param password - Developer's password
* @returns A promise that resolves to the login response with access and refresh tokens
*
* @description
* - Calls the login API endpoint
* - Stores access_token and refresh_token in localStorage
* - Updates the developer state with user information
* - Throws an error if authentication fails
*/
signIn: (email: string, password: string) => Promise<platformApi.PlatformLoginResponse>;

Expand All @@ -45,6 +61,12 @@ export type OpenSecretDeveloperContextType = {
* @param password - Developer's password
* @param name - Optional developer name
* @returns A promise that resolves to the login response with access and refresh tokens
*
* @description
* - Calls the registration API endpoint
* - Stores access_token and refresh_token in localStorage
* - Updates the developer state with new user information
* - Throws an error if account creation fails
*/
signUp: (
email: string,
Expand All @@ -54,8 +76,72 @@ export type OpenSecretDeveloperContextType = {

/**
* Signs out the current developer by removing authentication tokens
*
* @description
* - Calls the logout API endpoint with the current refresh_token
* - Removes access_token, refresh_token from localStorage
* - Resets the developer state to show no user is authenticated
*/
signOut: () => void;
signOut: () => Promise<void>;

/**
* Refreshes the developer's authentication state
* @returns A promise that resolves when the refresh is complete
* @throws {Error} If the refresh fails
*
* @description
* - Retrieves the latest developer information from the server
* - Updates the developer state with fresh data
* - Useful after making changes that affect developer profile or organization membership
*/
refetchDeveloper: () => Promise<void>;

/**
* Additional PCR0 hashes to validate against
*/
pcrConfig: PcrConfig;

/**
* Gets attestation from the enclave
*/
getAttestation: typeof getAttestation;

/**
* Authenticates an attestation document
*/
authenticate: typeof authenticate;

/**
* Parses an attestation document for viewing
*/
parseAttestationForView: (
document: AttestationDocument,
cabundle: Uint8Array[],
pcrConfig?: PcrConfig
) => Promise<ParsedAttestationView>;

/**
* AWS root certificate in DER format
*/
awsRootCertDer: typeof AWS_ROOT_CERT_DER;

/**
* Expected hash of the AWS root certificate
*/
expectedRootCertHash: typeof EXPECTED_ROOT_CERT_HASH;

/**
* Gets and verifies an attestation document from the enclave
* @returns A promise resolving to the parsed attestation document
* @throws {Error} If attestation fails or is invalid
*
* @description
* This is a convenience function that:
* 1. Fetches the attestation document with a random nonce
* 2. Authenticates the document
* 3. Parses it for viewing
*/
getAttestationDocument: () => Promise<ParsedAttestationView>;

/**
* Creates a new organization
Expand Down Expand Up @@ -234,11 +320,26 @@ export const OpenSecretDeveloperContext = createContext<OpenSecretDeveloperConte
loading: true,
developer: undefined
},
signIn: (email, password) => platformApi.platformLogin(email, password),
signUp: (email, password, name) => platformApi.platformRegister(email, password, name),
signOut: () => {
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
signIn: async () => {
throw new Error("signIn called outside of OpenSecretDeveloper provider");
},
signUp: async () => {
throw new Error("signUp called outside of OpenSecretDeveloper provider");
},
signOut: async () => {
throw new Error("signOut called outside of OpenSecretDeveloper provider");
},
refetchDeveloper: async () => {
throw new Error("refetchDeveloper called outside of OpenSecretDeveloper provider");
},
pcrConfig: {},
getAttestation,
authenticate,
parseAttestationForView,
awsRootCertDer: AWS_ROOT_CERT_DER,
expectedRootCertHash: EXPECTED_ROOT_CERT_HASH,
getAttestationDocument: async () => {
throw new Error("getAttestationDocument called outside of OpenSecretDeveloper provider");
},
createOrganization: platformApi.createOrganization,
listOrganizations: platformApi.listOrganizations,
Expand Down Expand Up @@ -281,10 +382,12 @@ export const OpenSecretDeveloperContext = createContext<OpenSecretDeveloperConte
*/
export function OpenSecretDeveloper({
children,
apiUrl
apiUrl,
pcrConfig = {}
}: {
children: React.ReactNode;
apiUrl: string;
pcrConfig?: PcrConfig;
}) {
const [developer, setDeveloper] = useState<OpenSecretDeveloperState>({
loading: true,
Expand Down Expand Up @@ -333,23 +436,81 @@ export function OpenSecretDeveloper({
});
}
}

const getAttestationDocument = async () => {
const nonce = window.crypto.randomUUID();
const response = await fetch(`${apiUrl}/attestation/${nonce}`);
if (!response.ok) {
throw new Error("Failed to fetch attestation document");
}

const data = await response.json();
const verifiedDocument = await authenticate(
data.attestation_document,
AWS_ROOT_CERT_DER,
nonce
);
return parseAttestationForView(verifiedDocument, verifiedDocument.cabundle, pcrConfig);
};

useEffect(() => {
fetchDeveloper();
}, []);

async function signIn(email: string, password: string) {
try {
const { access_token, refresh_token } = await platformApi.platformLogin(email, password);
window.localStorage.setItem("access_token", access_token);
window.localStorage.setItem("refresh_token", refresh_token);
await fetchDeveloper();
return { access_token, refresh_token, id: '', email };
} catch (error) {
console.error("Login error:", error);
throw error;
}
}

async function signUp(email: string, password: string, name?: string) {
try {
const { access_token, refresh_token } = await platformApi.platformRegister(email, password, name);
window.localStorage.setItem("access_token", access_token);
window.localStorage.setItem("refresh_token", refresh_token);
await fetchDeveloper();
return { access_token, refresh_token, id: '', email, name };
} catch (error) {
console.error("Registration error:", error);
throw error;
}
}

const value: OpenSecretDeveloperContextType = {
developer,
signIn: (email, password) => platformApi.platformLogin(email, password),
signUp: (email, password, name) => platformApi.platformRegister(email, password, name),
signOut: () => {
signIn,
signUp,
refetchDeveloper: fetchDeveloper,
signOut: async () => {
const refresh_token = window.localStorage.getItem("refresh_token");
if (refresh_token) {
try {
await platformApi.platformLogout(refresh_token);
} catch (error) {
console.error("Error during logout:", error);
}
}
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
setDeveloper({
loading: false,
developer: undefined
});
},
pcrConfig,
getAttestation,
authenticate,
parseAttestationForView,
awsRootCertDer: AWS_ROOT_CERT_DER,
expectedRootCertHash: EXPECTED_ROOT_CERT_HASH,
getAttestationDocument,
createOrganization: platformApi.createOrganization,
listOrganizations: platformApi.listOrganizations,
deleteOrganization: platformApi.deleteOrganization,
Expand Down
Loading

0 comments on commit 046f849

Please sign in to comment.