mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-01 19:32:40 +02:00
use api key instead of project secret
This commit is contained in:
parent
83591a690d
commit
e3576489e3
5 changed files with 29 additions and 24 deletions
|
|
@ -94,7 +94,7 @@ Before running RowBoat, ensure you have:
|
||||||
client = Client(
|
client = Client(
|
||||||
host="http://localhost:3000",
|
host="http://localhost:3000",
|
||||||
project_id="<PROJECT_ID>",
|
project_id="<PROJECT_ID>",
|
||||||
project_secret="<PROJECT_SECRET>"
|
api_key="<API_KEY>" # Generate this from /projects/<PROJECT_ID>/config
|
||||||
)
|
)
|
||||||
|
|
||||||
# Simple chat interaction
|
# Simple chat interaction
|
||||||
|
|
@ -108,12 +108,12 @@ Before running RowBoat, ensure you have:
|
||||||
|
|
||||||
You can use the API directly at [http://localhost:3000/api/v1/](http://localhost:3000/api/v1/)
|
You can use the API directly at [http://localhost:3000/api/v1/](http://localhost:3000/api/v1/)
|
||||||
- Project ID is available in the URL of the project page
|
- Project ID is available in the URL of the project page
|
||||||
- Project Secret is available in the project config page
|
- API Key can be generated from the project config page at `/projects/<PROJECT_ID>/config`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl --location 'http://localhost:3000/api/v1/<PROJECT_ID>/chat' \
|
curl --location 'http://localhost:3000/api/v1/<PROJECT_ID>/chat' \
|
||||||
--header 'Content-Type: application/json' \
|
--header 'Content-Type: application/json' \
|
||||||
--header 'Authorization: Bearer <PROJECT_SECRET>' \
|
--header 'Authorization: Bearer <API_KEY>' \
|
||||||
--data '{
|
--data '{
|
||||||
"messages": [
|
"messages": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "rowboat"
|
name = "rowboat"
|
||||||
version = "0.2.1"
|
version = "1.0.0"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Your Name", email = "your.email@example.com" },
|
{ name = "Your Name", email = "your.email@example.com" },
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,11 @@ from .schema import (
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
def __init__(self, host: str, project_id: str, project_secret: str) -> None:
|
def __init__(self, host: str, project_id: str, api_key: str) -> None:
|
||||||
self.base_url: str = f'{host}/api/v1/{project_id}/chat'
|
self.base_url: str = f'{host}/api/v1/{project_id}/chat'
|
||||||
self.headers: Dict[str, str] = {
|
self.headers: Dict[str, str] = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': f'Bearer {project_secret}'
|
'Authorization': f'Bearer {api_key}'
|
||||||
}
|
}
|
||||||
|
|
||||||
def _call_api(
|
def _call_api(
|
||||||
|
|
@ -167,8 +167,8 @@ def weather_lookup_tool(city_name: str) -> str:
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
host: str = "<HOST>"
|
host: str = "<HOST>"
|
||||||
project_id: str = "<PROJECT_ID>"
|
project_id: str = "<PROJECT_ID>"
|
||||||
project_secret: str = "<PROJECT_SECRET>"
|
api_key: str = "<API_KEY>"
|
||||||
client = Client(host, project_id, project_secret)
|
client = Client(host, project_id, api_key)
|
||||||
|
|
||||||
tools: Dict[str, Callable[..., str]] = {
|
tools: Dict[str, Callable[..., str]] = {
|
||||||
'weather_lookup': weather_lookup_tool
|
'weather_lookup': weather_lookup_tool
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,26 @@
|
||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
import { projectsCollection } from "@/app/lib/mongodb";
|
import { apiKeysCollection, projectsCollection } from "@/app/lib/mongodb";
|
||||||
|
|
||||||
export async function authCheck(projectId: string, req: NextRequest, handler: () => Promise<Response>): Promise<Response> {
|
export async function authCheck(projectId: string, req: NextRequest, handler: () => Promise<Response>): Promise<Response> {
|
||||||
const authHeader = req.headers.get('Authorization');
|
const authHeader = req.headers.get('Authorization');
|
||||||
if (!authHeader?.startsWith('Bearer ')) {
|
if (!authHeader?.startsWith('Bearer ')) {
|
||||||
return Response.json({ error: "Authorization header must be a Bearer token" }, { status: 400 });
|
return Response.json({ error: "Authorization header must be a Bearer token" }, { status: 400 });
|
||||||
}
|
}
|
||||||
const token = authHeader.split(' ')[1];
|
const key = authHeader.split(' ')[1];
|
||||||
if (!token) {
|
if (!key) {
|
||||||
return Response.json({ error: "Missing API key in request" }, { status: 400 });
|
return Response.json({ error: "Missing API key in request" }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the key in project settings
|
// check if api key is valid
|
||||||
const project = await projectsCollection.findOne({
|
// while also updating last used timestamp
|
||||||
_id: projectId,
|
const result = await apiKeysCollection.findOneAndUpdate(
|
||||||
secret: token,
|
{
|
||||||
});
|
projectId,
|
||||||
if (!project) {
|
key,
|
||||||
|
},
|
||||||
|
{ $set: { lastUsedAt: new Date().toISOString() } }
|
||||||
|
);
|
||||||
|
if (!result) {
|
||||||
return Response.json({ error: "Invalid API key" }, { status: 403 });
|
return Response.json({ error: "Invalid API key" }, { status: 403 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ import { getProjectConfig, updateProjectName, updateWebhookUrl, createApiKey, de
|
||||||
import { CopyButton } from "@/app/lib/components/copy-button";
|
import { CopyButton } from "@/app/lib/components/copy-button";
|
||||||
import { EditableField } from "@/app/lib/components/editable-field";
|
import { EditableField } from "@/app/lib/components/editable-field";
|
||||||
import { EyeIcon, EyeOffIcon, CopyIcon, MoreVerticalIcon, PlusIcon, EllipsisVerticalIcon } from "lucide-react";
|
import { EyeIcon, EyeOffIcon, CopyIcon, MoreVerticalIcon, PlusIcon, EllipsisVerticalIcon } from "lucide-react";
|
||||||
|
import { WithStringId, ApiKey } from "@/app/lib/types";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { RelativeTime } from "@primer/react";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Project config",
|
title: "Project config",
|
||||||
|
|
@ -142,11 +145,7 @@ export function ApiKeysSection({
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}) {
|
}) {
|
||||||
const [keys, setKeys] = useState<Array<{
|
const [keys, setKeys] = useState<WithStringId<z.infer<typeof ApiKey>>[]>([]);
|
||||||
_id: string;
|
|
||||||
key: string;
|
|
||||||
createdAt: string;
|
|
||||||
}>>([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [message, setMessage] = useState<{
|
const [message, setMessage] = useState<{
|
||||||
type: 'success' | 'error' | 'info';
|
type: 'success' | 'error' | 'info';
|
||||||
|
|
@ -254,9 +253,11 @@ export function ApiKeysSection({
|
||||||
<ApiKeyDisplay apiKey={key.key} />
|
<ApiKeyDisplay apiKey={key.key} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 p-2">
|
<div className="flex-1 p-2">
|
||||||
{new Date(key.createdAt).toLocaleDateString()}
|
<RelativeTime date={new Date(key.createdAt)} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 p-2">
|
||||||
|
{key.lastUsedAt ? <RelativeTime date={new Date(key.lastUsedAt)} /> : 'Never'}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 p-2">Never</div>
|
|
||||||
<div className="w-10 p-2">
|
<div className="w-10 p-2">
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<DropdownTrigger>
|
<DropdownTrigger>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue