Skip to content

Commit

Permalink
Platform user API and better login methods
Browse files Browse the repository at this point in the history
  • Loading branch information
AnthonyRonning committed Feb 25, 2025
1 parent 636b6d3 commit 99338e1
Show file tree
Hide file tree
Showing 9 changed files with 733 additions and 18 deletions.
370 changes: 370 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,376 @@ You can now use the OpenAI client as normal. (Right now only streaming responses

For an alternative approach using custom fetch directly, see the implementation in `src/lib/ai.test.ts` in the SDK source code.

## Developer Platform API

The Developer Platform API allows developers to manage organizations, projects, secrets, and user access within the OpenSecret platform. This API is specifically designed for developers integrating OpenSecret into their applications and platforms.

### `OpenSecretDeveloper`

The `OpenSecretDeveloper` component is the provider for developer-specific platform operations. It requires the URL of the OpenSecret developer API.

```tsx
import { OpenSecretDeveloper } from "@opensecret/react";

function App() {
return (
<OpenSecretDeveloper
apiUrl="https://developer.opensecret.cloud"
>
<YourApp />
</OpenSecretDeveloper>
);
}
```

### Developer Authentication

Before using the developer platform APIs, you need to authenticate. The SDK provides authentication methods through the `useOpenSecretDeveloper` hook:

```tsx
import { useOpenSecretDeveloper } from "@opensecret/react";

function DeveloperLogin() {
const dev = useOpenSecretDeveloper();

// Login with existing developer account
async function handleLogin() {
try {
const response = await dev.signIn("[email protected]", "yourpassword");
console.log("Login successful", response);
// Now you can use the developer context APIs
} catch (error) {
console.error("Login failed:", error);
}
}

// Register a new developer account
async function handleRegister() {
try {
const response = await dev.signUp(
"[email protected]",
"yourpassword",
"Developer Name" // Optional
);
console.log("Registration successful", response);
// Now you can use the developer context APIs
} catch (error) {
console.error("Registration failed:", error);
}
}

// Sign out
function handleLogout() {
dev.signOut();
// The developer context will update automatically
}

return (
<div>
{/* Login/Register UI */}
</div>
);
}
```

When a developer successfully logs in or registers, the authentication tokens are stored in localStorage and managed by the SDK. The `OpenSecretDeveloper` provider automatically detects these tokens and loads the developer profile. You can check the authentication state using the `developer` property:

```tsx
const dev = useOpenSecretDeveloper();

// Check if developer is loaded and authenticated
if (!dev.developer.loading && dev.developer.developer) {
console.log("Developer is authenticated:", dev.developer.developer.email);
} else if (!dev.developer.loading) {
console.log("Developer is not authenticated");
}
```

### `useOpenSecretDeveloper`

The `useOpenSecretDeveloper` hook provides access to all developer platform management APIs. It returns an object with the following properties and methods:

```tsx
import { useOpenSecretDeveloper } from "@opensecret/react";

function PlatformManagement() {
const dev = useOpenSecretDeveloper();

// Access developer information
const { loading, developer } = dev.developer;

// Now you can use any of the platform management methods
// ...
}
```

#### Developer State

- `developer`: An object containing the current developer's information
- `loading`: Boolean indicating whether developer information is being loaded
- `developer`: Developer data (undefined if not logged in) including:
- `id`: Developer's unique ID
- `email`: Developer's email address
- `name`: Developer's name (optional)
- `organizations`: Array of organizations the developer belongs to

#### Organization Management

- `createOrganization(name: string): Promise<Organization>`: Creates a new organization with the given name.
- `listOrganizations(): Promise<Organization[]>`: Lists all organizations the developer has access to.
- `deleteOrganization(orgId: string): Promise<void>`: Deletes an organization (requires owner role).

Example:
```tsx
const handleCreateOrg = async () => {
try {
const org = await dev.createOrganization("My New Organization");
console.log("Created organization:", org);
} catch (error) {
console.error("Failed to create organization:", error);
}
};
```

#### Project Management

- `createProject(orgId: string, name: string, description?: string): Promise<Project>`: Creates a new project within an organization.
- `listProjects(orgId: string): Promise<Project[]>`: Lists all projects within an organization.
- `updateProject(orgId: string, projectId: string, updates: { name?: string; description?: string; status?: string }): Promise<Project>`: Updates project details.
- `deleteProject(orgId: string, projectId: string): Promise<void>`: Deletes a project.

Example:
```tsx
const handleCreateProject = async (orgId) => {
try {
const project = await dev.createProject(
orgId,
"My New Project",
"A description of my project"
);
console.log("Created project:", project);
// project.client_id can be used as the clientId for OpenSecretProvider
} catch (error) {
console.error("Failed to create project:", error);
}
};
```

#### Project Secrets Management

- `createProjectSecret(orgId: string, projectId: string, keyName: string, secret: string): Promise<ProjectSecret>`: Creates a new secret for a project. The secret must be base64 encoded.
- `listProjectSecrets(orgId: string, projectId: string): Promise<ProjectSecret[]>`: Lists all secrets for a project.
- `deleteProjectSecret(orgId: string, projectId: string, keyName: string): Promise<void>`: Deletes a project secret.

Example:
```tsx
import { encode } from "@stablelib/base64";

const handleCreateSecret = async (orgId, projectId) => {
// Encode the secret
const secretValue = "my-secret-value";
const encodedSecret = encode(new TextEncoder().encode(secretValue));

try {
await dev.createProjectSecret(
orgId,
projectId,
"API_KEY",
encodedSecret
);
console.log("Secret created successfully");
} catch (error) {
console.error("Failed to create secret:", error);
}
};
```

#### Email Configuration

- `getEmailSettings(orgId: string, projectId: string): Promise<EmailSettings>`: Gets email configuration for a project.
- `updateEmailSettings(orgId: string, projectId: string, settings: EmailSettings): Promise<EmailSettings>`: Updates email configuration.

Example:
```tsx
const handleUpdateEmailSettings = async (orgId, projectId) => {
try {
await dev.updateEmailSettings(orgId, projectId, {
provider: "smtp",
send_from: "[email protected]",
email_verification_url: "https://yourdomain.com/verify-email"
});
console.log("Email settings updated");
} catch (error) {
console.error("Failed to update email settings:", error);
}
};
```

#### OAuth Configuration

- `getOAuthSettings(orgId: string, projectId: string): Promise<OAuthSettings>`: Gets OAuth settings for a project.
- `updateOAuthSettings(orgId: string, projectId: string, settings: OAuthSettings): Promise<OAuthSettings>`: Updates OAuth configuration.

Example:
```tsx
const handleUpdateOAuthSettings = async (orgId, projectId) => {
try {
await dev.updateOAuthSettings(orgId, projectId, {
google_oauth_enabled: true,
github_oauth_enabled: false,
google_oauth_settings: {
client_id: "your-google-client-id",
redirect_url: "https://yourdomain.com/auth/google/callback"
}
});
console.log("OAuth settings updated");
} catch (error) {
console.error("Failed to update OAuth settings:", error);
}
};
```

#### Developer Membership Management

- `inviteDeveloper(orgId: string, email: string, role?: string): Promise<{ code: string }>`: Creates an invitation to join an organization.
- `listOrganizationMembers(orgId: string): Promise<OrganizationMember[]>`: Lists all members of an organization.
- `updateMemberRole(orgId: string, userId: string, role: string): Promise<OrganizationMember>`: Updates a member's role.
- `removeMember(orgId: string, userId: string): Promise<void>`: Removes a member from the organization.
- `acceptInvite(code: string): Promise<void>`: Accepts an organization invitation.

Example:
```tsx
const handleInviteDeveloper = async (orgId) => {
try {
const result = await dev.inviteDeveloper(
orgId,
"[email protected]",
"admin" // Possible roles: "owner", "admin", "developer", "viewer"
);
console.log("Invitation sent with code:", result.code);
} catch (error) {
console.error("Failed to invite developer:", error);
}
};
```

### Complete Example

Here's a complete example of how to use the developer platform API:

```tsx
import React, { useEffect, useState } from 'react';
import { OpenSecretDeveloper, useOpenSecretDeveloper } from '@opensecret/react';

function DeveloperPortal() {
const dev = useOpenSecretDeveloper();
const [orgs, setOrgs] = useState([]);
const [selectedOrg, setSelectedOrg] = useState(null);
const [projects, setProjects] = useState([]);

useEffect(() => {
// Load organizations when developer is available
if (!dev.developer.loading && dev.developer.developer) {
loadOrganizations();
}
}, [dev.developer.loading, dev.developer.developer]);

async function loadOrganizations() {
try {
const orgs = await dev.listOrganizations();
setOrgs(orgs);
} catch (error) {
console.error("Failed to load organizations:", error);
}
}

async function loadProjects(orgId) {
try {
const projects = await dev.listProjects(orgId);
setProjects(projects);
} catch (error) {
console.error("Failed to load projects:", error);
}
}

async function handleCreateProject() {
if (!selectedOrg) return;

try {
await dev.createProject(
selectedOrg.id,
"New Project " + Date.now(),
"Created from developer portal"
);
// Reload projects
loadProjects(selectedOrg.id);
} catch (error) {
console.error("Failed to create project:", error);
}
}

return (
<div>
<h1>Developer Portal</h1>
{dev.developer.loading ? (
<p>Loading...</p>
) : dev.developer.developer ? (
<div>
<p>Welcome, {dev.developer.developer.name || dev.developer.developer.email}</p>

<h2>Your Organizations</h2>
<ul>
{orgs.map(org => (
<li key={org.id}>
{org.name}
<button onClick={() => {
setSelectedOrg(org);
loadProjects(org.id);
}}>
Select
</button>
</li>
))}
</ul>
<button onClick={() => {
const name = prompt("Organization name:");
if (name) dev.createOrganization(name).then(loadOrganizations);
}}>
Create Organization
</button>

{selectedOrg && (
<div>
<h2>Projects in {selectedOrg.name}</h2>
<ul>
{projects.map(project => (
<li key={project.id}>
{project.name} - Client ID: {project.client_id}
</li>
))}
</ul>
<button onClick={handleCreateProject}>Create Project</button>
</div>
)}
</div>
) : (
<p>Please log in to access developer features</p>
)}
</div>
);
}

function App() {
return (
<OpenSecretDeveloper apiUrl="https://developer.opensecret.cloud">
<DeveloperPortal />
</OpenSecretDeveloper>
);
}

export default App;
```

### Library development

This library uses [Bun](https://bun.sh/) for development.
Expand Down
Loading

0 comments on commit 99338e1

Please sign in to comment.