feat: add new auth_request flow for logging in with comfy deploy

This commit is contained in:
BennyKok
2024-01-22 14:11:14 +08:00
parent 3043093d22
commit 47168930dc
25 changed files with 2424 additions and 272 deletions
+13
View File
@@ -0,0 +1,13 @@
import { customAlphabet } from "nanoid";
export const nanoid = customAlphabet(
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
);
const prefixes = {
img: "img",
vid: "vid",
} as const;
export function newId(prefix: keyof typeof prefixes): string {
return [prefixes[prefix], nanoid(16)].join("_");
}
+3 -3
View File
@@ -1,10 +1,10 @@
import { createRun } from "../server/createRun";
import { db } from "@/db/db";
import { deploymentsTable } from "@/db/schema";
import type { App } from "@/routes/app";
import { authError } from "@/routes/authError";
import { z, createRoute } from "@hono/zod-openapi";
import { createRoute, z } from "@hono/zod-openapi";
import { eq } from "drizzle-orm";
import { createRun } from "../server/createRun";
const createRunRoute = createRoute({
method: "post",
@@ -99,7 +99,7 @@ export const registerCreateRunRoute = (app: App) => {
},
{
status: 500,
}
},
);
}
});
+150
View File
@@ -0,0 +1,150 @@
import { db } from "@/db/db";
import { authRequestsTable } from "@/db/schema";
import type { App } from "@/routes/app";
import { authError } from "@/routes/authError";
import { z, createRoute } from "@hono/zod-openapi";
import { eq } from "drizzle-orm";
import jwt from "jsonwebtoken";
import crypto from "crypto";
import { getOrgOrUserDisplayName } from "@/server/getOrgOrUserDisplayName";
import ms from "ms";
const route = createRoute({
method: "get",
path: "/auth-response/:request_id",
tags: ["comfyui"],
summary: "Get an API Key with code",
description:
"This endpoints is specifically built for ComfyUI workflow upload.",
request: {
params: z.object({
request_id: z.string(),
}),
},
responses: {
200: {
content: {
"application/json": {
schema: z.object({
api_key: z.string(),
name: z.string(),
}),
},
},
description: "The returned API Key",
},
201: {
content: {
"application/json": {
schema: z.object({
message: z.string(),
}),
},
},
description: "The API key is not yet ready",
},
500: {
content: {
"application/json": {
schema: z.object({
error: z.string(),
}),
},
},
description: "Error when fetching the API Key with code",
},
...authError,
},
});
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
export const registerGetAuthResponse = (app: App) => {
return app.openapi(route, async (c) => {
const { request_id } = c.req.valid("param");
try {
const result = await db.query.authRequestsTable.findFirst({
where: eq(authRequestsTable.request_id, request_id),
});
if (result?.api_hash) {
return c.json(
{
message: "Already used.",
},
{
status: 201,
headers: corsHeaders,
},
);
}
if (result && result.user_id) {
const expireTime = "1w";
const token = jwt.sign(
{ user_id: result.user_id, org_id: result.org_id },
process.env.JWT_SECRET!,
{
expiresIn: expireTime,
},
);
const hash = crypto.createHash("sha256").update(token).digest("hex");
const now = new Date();
const expiryDate = new Date(now.getTime() + ms(expireTime));
await db
.update(authRequestsTable)
.set({
api_hash: hash,
expired_date: expiryDate,
})
.where(eq(authRequestsTable.request_id, request_id));
const userName = await getOrgOrUserDisplayName(
result.org_id,
result.user_id,
);
return c.json(
{
api_key: token,
name: userName,
},
{
status: 200,
headers: corsHeaders,
},
);
}
} catch (error: unknown) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
return c.json(
{
error: errorMessage,
},
{
statusText: "Invalid request",
status: 500,
headers: corsHeaders,
},
);
}
return c.json(
{
message: "Not ready yet.",
},
{
status: 201,
headers: corsHeaders,
},
);
});
};
+3 -16
View File
@@ -3,20 +3,7 @@ import { authError } from "@/routes/authError";
import { getFileDownloadUrl } from "@/server/getFileDownloadUrl";
import { handleResourceUpload } from "@/server/resource";
import { z, createRoute } from "@hono/zod-openapi";
import { customAlphabet } from "nanoid";
export const nanoid = customAlphabet(
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
);
const prefixes = {
img: "img",
vid: "vid",
} as const;
export function newId(prefix: keyof typeof prefixes): string {
return [prefixes[prefix], nanoid(16)].join("_");
}
import { newId } from "./newId";
const uploadUrlRoute = createRoute({
method: "get",
@@ -96,7 +83,7 @@ export const registerUploadRoute = (app: App) => {
file_id: id,
download_url: await getFileDownloadUrl(filePath),
},
200
200,
);
} catch (error: unknown) {
const errorMessage =
@@ -107,7 +94,7 @@ export const registerUploadRoute = (app: App) => {
},
{
status: 500,
}
},
);
}
});
@@ -0,0 +1,164 @@
import { snapshotType, workflowAPIType, workflowType } from "@/db/schema";
import type { App } from "@/routes/app";
import { authError } from "@/routes/authError";
import {
createNewWorkflow,
createNewWorkflowVersion,
} from "@/server/createNewWorkflow";
import { z, createRoute } from "@hono/zod-openapi";
const route = createRoute({
method: "post",
path: "/upload-workflow",
tags: ["comfyui"],
summary: "Upload workflow from ComfyUI",
description:
"This endpoints is specifically built for ComfyUI workflow upload.",
request: {
body: {
content: {
"application/json": {
schema: z.object({
workflow_id: z.string().optional(),
workflow_name: z.string().min(1).optional(),
workflow: workflowType,
workflow_api: workflowAPIType,
snapshot: snapshotType,
}),
},
},
},
},
responses: {
200: {
content: {
"application/json": {
schema: z.object({
workflow_id: z.string(),
version: z.string(),
}),
},
},
description: "Retrieve the output",
},
500: {
content: {
"application/json": {
schema: z.object({
error: z.string(),
}),
},
},
description: "Error when uploading the workflow",
},
...authError,
},
});
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
export const registerWorkflowUploadRoute = (app: App) => {
app.openapi(route, async (c) => {
const {
// user_id,
workflow,
workflow_api,
workflow_id: _workflow_id,
workflow_name,
snapshot,
} = c.req.valid("json");
const { org_id, user_id } = c.get("apiKeyTokenData")!;
if (!user_id)
return c.json(
{
error: "Invalid user_id",
},
{
headers: corsHeaders,
status: 500,
},
);
let workflow_id = _workflow_id;
let version = -1;
try {
if ((!workflow_id || workflow_id.length === 0) && workflow_name) {
// Create a new parent workflow
const { workflow_id: _workflow_id, version: _version } =
await createNewWorkflow({
user_id: user_id,
org_id: org_id,
workflow_name: workflow_name,
workflowData: {
workflow,
workflow_api,
snapshot,
},
});
workflow_id = _workflow_id;
version = _version;
} else if (workflow_id) {
// Case 2 update workflow
const { version: _version } = await createNewWorkflowVersion({
workflow_id: workflow_id,
workflowData: {
workflow,
workflow_api,
snapshot,
},
});
version = _version;
} else {
return c.json(
{
error: "Invalid request, missing either workflow_id or name",
},
{
status: 500,
statusText: "Invalid request",
headers: corsHeaders,
},
);
}
} catch (error: unknown) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
return c.json(
{
error: errorMessage,
},
{
statusText: "Invalid request",
status: 500,
headers: corsHeaders,
},
);
}
return c.json(
{
workflow_id: workflow_id,
version: version,
},
{
status: 200,
headers: corsHeaders,
},
);
});
app.route("/upload-workflow").options(async (c) => {
return new Response(null, {
status: 204,
headers: corsHeaders,
});
});
};