Skip to content

Commit

Permalink
fix: Replace public key auth with token-based system
Browse files Browse the repository at this point in the history
  • Loading branch information
nadeesha committed Dec 31, 2024
1 parent 3330361 commit 2b3b227
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 89 deletions.
2 changes: 1 addition & 1 deletion adapters/valtown-adapter/jsr.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@inferable/valtown-adapter",
"version": "0.0.5",
"version": "0.0.10",
"license": "MIT",
"exports": "./mod.ts"
}
137 changes: 49 additions & 88 deletions adapters/valtown-adapter/mod.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,13 @@
const {
createVerify,
} = await import('node:crypto');

export class InferableService {
private functions: Parameters<InferableService["registerFunction"]>[0][] = [];

constructor(
private options: {
description: string;
publicKey: string;
token?: string;
},
) {
if (this.options.publicKey) {
const hasPrefix = this.options.publicKey.startsWith("-----BEGIN PUBLIC KEY-----");

if (!hasPrefix) {
const base64Content = this.options.publicKey;
const lines = base64Content.match(/.{1,64}/g) || [];
const formatted = [
"-----BEGIN PUBLIC KEY-----",
...lines,
"-----END PUBLIC KEY-----",
].join("\n");

this.options.publicKey = formatted;
}
}
// Remove public key formatting logic as it's no longer needed
}

registerFunction(options: {
Expand All @@ -43,100 +25,71 @@ export class InferableService {
this.functions.push(options);
}

private isAuthenticated({
xTimestamp,
xSignature,
method,
path,
body,
}: {
xTimestamp: string;
xSignature: string;
method: string;
path: string;
body: string;
}): boolean {
const signatureFromHeader = xSignature;

if (!signatureFromHeader) {
return false;
private isAuthenticated(token: string | null): boolean {
if (!this.options.token) {
return true; // If no token is configured, authentication is disabled
}

console.log("About to verify", {
xTimestamp,
method,
path,
body,
signatureFromHeader,
publicKey: this.options.publicKey,
});

const verifier = createVerify("SHA256");
const message = `${xTimestamp}${method}${path}${body}`;
console.log("Message to verify:", message);
verifier.update(message);
verifier.end();
const result = verifier.verify(this.options.publicKey, signatureFromHeader, "hex");
console.log("Verification result:", result);
return result;
return token === this.options.token;
}

getServer(): (request: Request) => Promise<Response> {
const server = async (request: Request): Promise<Response> => {
console.log(`[Inferable Adapter] Incoming request to: ${request.url}`);

const url = new URL(request.url);
const path = url.pathname;

const hasPublicKey = this.options.publicKey !== undefined;
const hasToken = this.options.token !== undefined;
const authHeader = request.headers.get("Authorization");
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;

if (!hasPublicKey) {
console.warn("No public key provided. Authentication is disabled. See https://docs.inferable.ai/valtown to learn how to enable it.");
console.log(`[Inferable Adapter] Authentication status: hasToken=${hasToken}, token=${token ? 'provided' : 'not provided'}`);

if (!hasToken) {
console.warn("[Inferable Adapter] No token provided. Authentication is disabled. See https://docs.inferable.ai/valtown to learn how to enable it.");
}

// Metadata route
if (path === "/meta") {
return new Response(
JSON.stringify({
description: this.options.description,
functions: Object.values(this.functions).map(func => ({
name: func.name,
description: func.description,
input: func.input,
})),
}),
{
console.log("[Inferable Adapter] Handling /meta request");
if (hasToken && !this.isAuthenticated(token)) {
console.log("[Inferable Adapter] /meta request unauthorized");
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "content-type": "application/json" },
},
);
});
}

const metadata = {
description: this.options.description,
functions: Object.values(this.functions).map(func => ({
name: func.name,
description: func.description,
input: func.input,
})),
};
console.log("[Inferable Adapter] Returning metadata:", metadata);
return new Response(JSON.stringify(metadata), {
headers: { "content-type": "application/json" },
});
}

// Execution route
if (path.startsWith("/exec/functions/")) {
const body = await request.body?.getReader().read();
const bodyText = body ? new TextDecoder().decode(body.value) : "";

if (!bodyText) {
return new Response(JSON.stringify({ error: "No body" }), {
status: 400,
headers: { "content-type": "application/json" },
});
}

if (hasPublicKey && !this.isAuthenticated({
xTimestamp: request.headers.get("X-Timestamp") || "",
xSignature: request.headers.get("X-Signature") || "",
method: request.method,
path,
body: bodyText,
})) {
console.log("[Inferable Adapter] Handling function execution request");
if (hasToken && !this.isAuthenticated(token)) {
console.log("[Inferable Adapter] Function execution unauthorized");
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "content-type": "application/json" },
});
}

const functionName = path.split("/")[3];
console.log(`[Inferable Adapter] Attempting to execute function: ${functionName}`);

if (request.method !== "POST") {
console.log(`[Inferable Adapter] Invalid method ${request.method} for function execution`);
return new Response(JSON.stringify({ error: "Method not allowed" }), {
status: 405,
headers: { "content-type": "application/json" },
Expand All @@ -145,18 +98,25 @@ export class InferableService {

try {
const input = await request.json();
console.log(`[Inferable Adapter] Function input:`, input);

const handler = this.functions.find(func => func.name === functionName);

if (!handler) {
console.log(`[Inferable Adapter] Function ${functionName} not found`);
return new Response(JSON.stringify({ error: `Function ${functionName} not found` }), {
status: 404,
headers: { "content-type": "application/json" },
});
}

return new Response(JSON.stringify({ result: await handler.handler(input) }));
console.log(`[Inferable Adapter] Executing function ${functionName}`);
const result = await handler.handler(input);
console.log(`[Inferable Adapter] Function ${functionName} result:`, result);

return new Response(JSON.stringify({ result }));
} catch (error) {
console.error(`[Inferable Adapter] Error executing function:`, error);
return new Response(JSON.stringify({ error: `Error occurred during execution: ${error}` }), {
status: 400,
headers: { "content-type": "application/json" },
Expand All @@ -165,6 +125,7 @@ export class InferableService {
}

// Default route
console.log(`[Inferable Adapter] Route not found: ${path}`);
return new Response(
JSON.stringify({
error: `Route not found: ${path}`,
Expand Down

0 comments on commit 2b3b227

Please sign in to comment.