Merge branch 'dev'
This commit is contained in:
commit
abf88e0ceb
|
|
@ -30,6 +30,7 @@ services:
|
|||
- INFISICAL_PROJECT_SLUG=rspace
|
||||
- INFISICAL_ENV=prod
|
||||
- INFISICAL_URL=http://infisical:8080
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- FLOW_SERVICE_URL=http://payment-flow:3010
|
||||
- FLOW_ID=a79144ec-e6a2-4e30-a42a-6d8237a5953d
|
||||
- FUNNEL_ID=0ff6a9ac-1667-4fc7-9a01-b1620810509f
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
* Canvas Tool Registry — shared by server (Gemini function declarations) and client (shape spawning).
|
||||
* Pure TypeScript, no DOM or server dependencies.
|
||||
*/
|
||||
|
||||
export interface CanvasToolDefinition {
|
||||
declaration: {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: {
|
||||
type: "object";
|
||||
properties: Record<string, { type: string; description: string; enum?: string[] }>;
|
||||
required: string[];
|
||||
};
|
||||
};
|
||||
tagName: string;
|
||||
buildProps: (args: Record<string, any>) => Record<string, any>;
|
||||
actionLabel: (args: Record<string, any>) => string;
|
||||
}
|
||||
|
||||
const registry: CanvasToolDefinition[] = [
|
||||
{
|
||||
declaration: {
|
||||
name: "create_map",
|
||||
description: "Create an interactive map centered on a location. Use when the user wants to see a place, get directions, or explore a geographic area.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
latitude: { type: "number", description: "Latitude of the center point" },
|
||||
longitude: { type: "number", description: "Longitude of the center point" },
|
||||
zoom: { type: "number", description: "Zoom level (1-18, default 12)" },
|
||||
location_name: { type: "string", description: "Human-readable name of the location" },
|
||||
},
|
||||
required: ["latitude", "longitude", "location_name"],
|
||||
},
|
||||
},
|
||||
tagName: "folk-map",
|
||||
buildProps: (args) => ({
|
||||
center: [args.longitude, args.latitude],
|
||||
zoom: args.zoom || 12,
|
||||
}),
|
||||
actionLabel: (args) => `Created map: ${args.location_name}`,
|
||||
},
|
||||
{
|
||||
declaration: {
|
||||
name: "create_note",
|
||||
description: "Create a markdown note on the canvas. Use for text content, lists, summaries, instructions, or any written information.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
content: { type: "string", description: "Markdown content for the note" },
|
||||
title: { type: "string", description: "Optional title for the note" },
|
||||
},
|
||||
required: ["content"],
|
||||
},
|
||||
},
|
||||
tagName: "folk-markdown",
|
||||
buildProps: (args) => ({
|
||||
value: args.title ? `# ${args.title}\n\n${args.content}` : args.content,
|
||||
}),
|
||||
actionLabel: (args) => `Created note${args.title ? `: ${args.title}` : ""}`,
|
||||
},
|
||||
{
|
||||
declaration: {
|
||||
name: "create_embed",
|
||||
description: "Embed a webpage or web app on the canvas. Use for websites, search results, booking sites, videos, or any URL the user wants to view inline.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
url: { type: "string", description: "The URL to embed" },
|
||||
title: { type: "string", description: "Descriptive title for the embed" },
|
||||
},
|
||||
required: ["url"],
|
||||
},
|
||||
},
|
||||
tagName: "folk-embed",
|
||||
buildProps: (args) => ({
|
||||
url: args.url,
|
||||
}),
|
||||
actionLabel: (args) => `Embedded: ${args.title || args.url}`,
|
||||
},
|
||||
{
|
||||
declaration: {
|
||||
name: "create_image",
|
||||
description: "Display an image on the canvas from a URL. Use when showing an existing image, photo, diagram, or any direct image link.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
src: { type: "string", description: "Image URL" },
|
||||
alt: { type: "string", description: "Alt text describing the image" },
|
||||
},
|
||||
required: ["src"],
|
||||
},
|
||||
},
|
||||
tagName: "folk-image",
|
||||
buildProps: (args) => ({
|
||||
src: args.src,
|
||||
alt: args.alt || "",
|
||||
}),
|
||||
actionLabel: (args) => `Created image${args.alt ? `: ${args.alt}` : ""}`,
|
||||
},
|
||||
{
|
||||
declaration: {
|
||||
name: "create_bookmark",
|
||||
description: "Create a bookmark card for a URL. Use when the user wants to save or reference a link without embedding the full page.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
url: { type: "string", description: "The URL to bookmark" },
|
||||
},
|
||||
required: ["url"],
|
||||
},
|
||||
},
|
||||
tagName: "folk-bookmark",
|
||||
buildProps: (args) => ({
|
||||
url: args.url,
|
||||
}),
|
||||
actionLabel: (args) => `Bookmarked: ${args.url}`,
|
||||
},
|
||||
{
|
||||
declaration: {
|
||||
name: "create_image_gen",
|
||||
description: "Generate an AI image from a text prompt. Use when the user wants to create, generate, or imagine a new image that doesn't exist yet.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
prompt: { type: "string", description: "Text prompt describing the image to generate" },
|
||||
style: {
|
||||
type: "string",
|
||||
description: "Visual style for the generated image",
|
||||
enum: ["photorealistic", "illustration", "painting", "sketch", "punk-zine", "collage", "vintage", "minimalist"],
|
||||
},
|
||||
},
|
||||
required: ["prompt"],
|
||||
},
|
||||
},
|
||||
tagName: "folk-image-gen",
|
||||
buildProps: (args) => ({
|
||||
prompt: args.prompt,
|
||||
style: args.style || "photorealistic",
|
||||
}),
|
||||
actionLabel: (args) => `Generating image: ${args.prompt.slice(0, 50)}${args.prompt.length > 50 ? "..." : ""}`,
|
||||
},
|
||||
];
|
||||
|
||||
export const CANVAS_TOOLS: CanvasToolDefinition[] = [...registry];
|
||||
|
||||
export const CANVAS_TOOL_DECLARATIONS = CANVAS_TOOLS.map((t) => t.declaration);
|
||||
|
||||
export function findTool(name: string): CanvasToolDefinition | undefined {
|
||||
return CANVAS_TOOLS.find((t) => t.declaration.name === name);
|
||||
}
|
||||
|
||||
export function registerCanvasTool(def: CanvasToolDefinition): void {
|
||||
CANVAS_TOOLS.push(def);
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { FolkShape } from "./folk-shape";
|
||||
import { css, html } from "./tags";
|
||||
import { SpeechDictation } from "./speech-dictation";
|
||||
import { findTool } from "./canvas-tools";
|
||||
|
||||
const styles = css`
|
||||
:host {
|
||||
|
|
@ -304,11 +305,50 @@ const styles = css`
|
|||
code {
|
||||
font-family: "Monaco", "Consolas", monospace;
|
||||
}
|
||||
|
||||
.input-controls {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tools-btn {
|
||||
padding: 5px 10px;
|
||||
border: 2px solid var(--rs-input-border, #e2e8f0);
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
background: var(--rs-input-bg, #fff);
|
||||
color: var(--rs-text-muted, #94a3b8);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tools-btn:hover {
|
||||
border-color: #6366f1;
|
||||
}
|
||||
|
||||
.tools-btn.active {
|
||||
background: #6366f1;
|
||||
border-color: #6366f1;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.message.tool-action {
|
||||
align-self: center;
|
||||
background: #ecfdf5;
|
||||
color: #065f46;
|
||||
border-radius: 20px;
|
||||
padding: 6px 14px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
max-width: 90%;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string;
|
||||
role: "user" | "assistant";
|
||||
role: "user" | "assistant" | "tool-action";
|
||||
content: string;
|
||||
images?: string[];
|
||||
timestamp: Date;
|
||||
|
|
@ -345,6 +385,7 @@ export class FolkPrompt extends FolkShape {
|
|||
#error: string | null = null;
|
||||
#model = "gemini-flash";
|
||||
#pendingImages: string[] = [];
|
||||
#toolsEnabled = false;
|
||||
|
||||
#messagesEl: HTMLElement | null = null;
|
||||
#promptInput: HTMLTextAreaElement | null = null;
|
||||
|
|
@ -352,6 +393,7 @@ export class FolkPrompt extends FolkShape {
|
|||
#sendBtn: HTMLButtonElement | null = null;
|
||||
#attachInput: HTMLInputElement | null = null;
|
||||
#pendingImagesEl: HTMLElement | null = null;
|
||||
#toolsBtn: HTMLButtonElement | null = null;
|
||||
|
||||
get messages() {
|
||||
return this.#messages;
|
||||
|
|
@ -381,18 +423,21 @@ export class FolkPrompt extends FolkShape {
|
|||
</div>
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<select class="model-select">
|
||||
<optgroup label="Gemini">
|
||||
<option value="gemini-flash">Gemini 2.5 Flash</option>
|
||||
<option value="gemini-pro">Gemini 2.5 Pro</option>
|
||||
</optgroup>
|
||||
<optgroup label="Local (Ollama)">
|
||||
<option value="llama3.2">Llama 3.2 (3B)</option>
|
||||
<option value="llama3.1">Llama 3.1 (8B)</option>
|
||||
<option value="qwen2.5-coder">Qwen Coder (7B)</option>
|
||||
<option value="mistral-small">Mistral Small (24B)</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<div class="input-controls">
|
||||
<select class="model-select">
|
||||
<optgroup label="Gemini">
|
||||
<option value="gemini-flash">Gemini 2.5 Flash</option>
|
||||
<option value="gemini-pro">Gemini 2.5 Pro</option>
|
||||
</optgroup>
|
||||
<optgroup label="Local (Ollama)">
|
||||
<option value="llama3.2">Llama 3.2 (3B)</option>
|
||||
<option value="llama3.1">Llama 3.1 (8B)</option>
|
||||
<option value="qwen2.5-coder">Qwen Coder (7B)</option>
|
||||
<option value="mistral-small">Mistral Small (24B)</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<button class="tools-btn" title="Enable canvas tools — AI can create maps, notes, embeds, and more">Tools</button>
|
||||
</div>
|
||||
<div class="pending-images"></div>
|
||||
<div class="prompt-row">
|
||||
<textarea class="prompt-input" placeholder="Type your message..." rows="2"></textarea>
|
||||
|
|
@ -418,10 +463,23 @@ export class FolkPrompt extends FolkShape {
|
|||
this.#sendBtn = wrapper.querySelector(".send-btn");
|
||||
this.#attachInput = wrapper.querySelector(".attach-input");
|
||||
this.#pendingImagesEl = wrapper.querySelector(".pending-images");
|
||||
this.#toolsBtn = wrapper.querySelector(".tools-btn") as HTMLButtonElement;
|
||||
const clearBtn = wrapper.querySelector(".clear-btn") as HTMLButtonElement;
|
||||
const closeBtn = wrapper.querySelector(".close-btn") as HTMLButtonElement;
|
||||
const attachBtn = wrapper.querySelector(".attach-btn") as HTMLButtonElement | null;
|
||||
|
||||
// Tools toggle
|
||||
this.#toolsBtn?.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
this.#toolsEnabled = !this.#toolsEnabled;
|
||||
this.#toolsBtn!.classList.toggle("active", this.#toolsEnabled);
|
||||
if (this.#promptInput) {
|
||||
this.#promptInput.placeholder = this.#toolsEnabled
|
||||
? "Ask me to create maps, notes, images, or embeds on the canvas..."
|
||||
: "Type your message...";
|
||||
}
|
||||
});
|
||||
|
||||
// Attach button
|
||||
attachBtn?.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -566,16 +624,18 @@ export class FolkPrompt extends FolkShape {
|
|||
this.#renderMessages(true);
|
||||
|
||||
try {
|
||||
const useTools = this.#toolsEnabled && this.#model.startsWith("gemini");
|
||||
const response = await fetch("/api/prompt", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
messages: this.#messages.map((m) => ({
|
||||
messages: this.#messages.filter((m) => m.role !== "tool-action").map((m) => ({
|
||||
role: m.role,
|
||||
content: m.content,
|
||||
...(m.images?.length ? { images: m.images } : {}),
|
||||
})),
|
||||
model: this.#model,
|
||||
...(useTools ? { useTools: true } : {}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
@ -585,6 +645,11 @@ export class FolkPrompt extends FolkShape {
|
|||
|
||||
const result = await response.json();
|
||||
|
||||
// Execute tool calls if any
|
||||
if (result.toolCalls?.length) {
|
||||
this.#executeToolCalls(result.toolCalls);
|
||||
}
|
||||
|
||||
const assistantMessage: ChatMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
role: "assistant",
|
||||
|
|
@ -607,6 +672,42 @@ export class FolkPrompt extends FolkShape {
|
|||
}
|
||||
}
|
||||
|
||||
#executeToolCalls(toolCalls: { name: string; args: Record<string, any>; label: string }[]) {
|
||||
const api = (window as any).__canvasApi;
|
||||
if (!api) {
|
||||
console.warn("[folk-prompt] Canvas API not available — cannot spawn shapes");
|
||||
return;
|
||||
}
|
||||
|
||||
let offsetY = 0;
|
||||
for (const tc of toolCalls) {
|
||||
const tool = findTool(tc.name);
|
||||
if (!tool) continue;
|
||||
|
||||
const props = tool.buildProps(tc.args);
|
||||
const defaults = api.SHAPE_DEFAULTS?.[tool.tagName] || { width: 300, height: 200 };
|
||||
const preferX = this.x + this.width + 40 + defaults.width / 2;
|
||||
const preferY = this.y + offsetY + defaults.height / 2;
|
||||
const pos = api.findFreePosition(defaults.width, defaults.height, preferX, preferY, this);
|
||||
|
||||
try {
|
||||
api.newShape(tool.tagName, props, { x: pos.x + defaults.width / 2, y: pos.y + defaults.height / 2 });
|
||||
} catch (e) {
|
||||
console.error(`[folk-prompt] Failed to create ${tool.tagName}:`, e);
|
||||
}
|
||||
|
||||
// Tool action card in chat
|
||||
this.#messages.push({
|
||||
id: crypto.randomUUID(),
|
||||
role: "tool-action",
|
||||
content: tc.label,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
|
||||
offsetY += defaults.height + 20;
|
||||
}
|
||||
}
|
||||
|
||||
#clearChat() {
|
||||
this.#messages = [];
|
||||
this.#error = null;
|
||||
|
|
@ -629,6 +730,9 @@ export class FolkPrompt extends FolkShape {
|
|||
|
||||
let messagesHtml = this.#messages
|
||||
.map((msg) => {
|
||||
if (msg.role === "tool-action") {
|
||||
return `<div class="message tool-action">${this.#escapeHtml(msg.content)}</div>`;
|
||||
}
|
||||
let imgHtml = "";
|
||||
if (msg.images?.length) {
|
||||
imgHtml = `<div class="msg-images">${msg.images.map((src) => `<img src="${this.#escapeHtml(src)}" />`).join("")}</div>`;
|
||||
|
|
@ -684,7 +788,7 @@ export class FolkPrompt extends FolkShape {
|
|||
...super.toJSON(),
|
||||
type: "folk-prompt",
|
||||
model: this.#model,
|
||||
messages: this.messages.map((msg) => ({
|
||||
messages: this.messages.filter((m) => m.role !== "tool-action").map((msg) => ({
|
||||
role: msg.role,
|
||||
content: msg.content,
|
||||
...(msg.images?.length ? { images: msg.images } : {}),
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import * as Automerge from "@automerge/automerge";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import { bnbSchema, bnbDocId } from './schemas';
|
||||
|
|
@ -573,7 +573,7 @@ routes.get("/api/listings", async (c) => {
|
|||
routes.post("/api/listings", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -637,7 +637,7 @@ routes.get("/api/listings/:id", async (c) => {
|
|||
routes.patch("/api/listings/:id", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -721,7 +721,7 @@ routes.get("/api/listings/:id/availability", async (c) => {
|
|||
routes.post("/api/listings/:id/availability", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -755,7 +755,7 @@ routes.post("/api/listings/:id/availability", async (c) => {
|
|||
routes.patch("/api/availability/:id", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -815,7 +815,7 @@ routes.get("/api/stays", async (c) => {
|
|||
routes.post("/api/stays", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -888,7 +888,7 @@ function stayTransition(statusTarget: StayStatus, timestampField: 'respondedAt'
|
|||
return async (c: any) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -918,7 +918,7 @@ routes.post("/api/stays/:id/complete", stayTransition('completed', 'completedAt'
|
|||
routes.post("/api/stays/:id/messages", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -968,7 +968,7 @@ routes.get("/api/endorsements", async (c) => {
|
|||
routes.post("/api/endorsements", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -1167,7 +1167,7 @@ routes.get("/api/config", async (c) => {
|
|||
routes.patch("/api/config", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
|
|||
|
|
@ -17,10 +17,7 @@ import { renderShell } from "../../server/shell";
|
|||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule, SpaceLifecycleContext } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
import {
|
||||
verifyEncryptIDToken,
|
||||
extractToken,
|
||||
} from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import {
|
||||
booksCatalogSchema,
|
||||
|
|
@ -163,7 +160,7 @@ routes.post("/api/books", async (c) => {
|
|||
|
||||
let claims;
|
||||
try {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
} catch {
|
||||
return c.json({ error: "Invalid token" }, 401);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import * as Automerge from "@automerge/automerge";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import { calendarSchema, calendarDocId } from './schemas';
|
||||
|
|
@ -315,7 +315,7 @@ routes.post("/api/events", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -404,7 +404,7 @@ routes.post("/api/events", async (c) => {
|
|||
routes.post("/api/import-ics", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -573,7 +573,7 @@ routes.patch("/api/events/:id", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -672,7 +672,7 @@ routes.get("/api/sources", async (c) => {
|
|||
routes.post("/api/sources", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { renderShell, buildSpaceUrl } from "../../server/shell";
|
|||
import { getModuleInfoList } from "../../shared/module";
|
||||
import { depositOrderRevenue } from "./flow";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import {
|
||||
|
|
@ -484,7 +484,7 @@ routes.post("/api/orders", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
let buyerDid: string | null = null;
|
||||
if (token) {
|
||||
try { const claims = await verifyEncryptIDToken(token); buyerDid = claims.sub; } catch {}
|
||||
try { const claims = await verifyToken(token); buyerDid = claims.sub; } catch {}
|
||||
}
|
||||
|
||||
const body = await c.req.json();
|
||||
|
|
@ -567,7 +567,7 @@ routes.get("/api/orders", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
let authedBuyer: string | null = null;
|
||||
if (token) {
|
||||
try { const claims = await verifyEncryptIDToken(token); authedBuyer = claims.sub; } catch {}
|
||||
try { const claims = await verifyToken(token); authedBuyer = claims.sub; } catch {}
|
||||
}
|
||||
|
||||
const { status, provider_id, buyer_id, limit = "50", offset = "0" } = c.req.query();
|
||||
|
|
@ -1426,7 +1426,7 @@ routes.post("/api/payments", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const {
|
||||
|
|
@ -1512,7 +1512,7 @@ routes.get("/api/payments", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const paymentDocs = getSpacePaymentDocs(space);
|
||||
const payments = paymentDocs
|
||||
|
|
@ -2251,7 +2251,7 @@ routes.post("/api/group-buys", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { catalogEntryId, tiers, description, closesInDays } = body;
|
||||
|
|
@ -2371,7 +2371,7 @@ routes.post("/api/group-buys/:id/pledge", async (c) => {
|
|||
let buyerId: string | null = null;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (token) {
|
||||
try { const claims = await verifyEncryptIDToken(token); buyerId = claims.sub || null; } catch { /* public pledge */ }
|
||||
try { const claims = await verifyToken(token); buyerId = claims.sub || null; } catch { /* public pledge */ }
|
||||
}
|
||||
|
||||
const pledgeId = crypto.randomUUID();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import * as Automerge from "@automerge/automerge";
|
|||
import { renderShell, renderExternalAppShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import { filesSchema, filesDocId } from './schemas';
|
||||
|
|
@ -166,7 +166,7 @@ routes.post("/api/files", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const formData = await c.req.formData();
|
||||
const file = formData.get("file") as File | null;
|
||||
|
|
@ -300,7 +300,7 @@ routes.post("/api/files/:id/share", async (c) => {
|
|||
const authToken = extractToken(c.req.raw.headers);
|
||||
if (!authToken) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const fileId = c.req.param("id");
|
||||
const space = c.req.param("space") || c.req.query("space") || "default";
|
||||
|
|
@ -376,7 +376,7 @@ routes.post("/api/shares/:shareId/revoke", async (c) => {
|
|||
const authToken = extractToken(c.req.raw.headers);
|
||||
if (!authToken) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const shareId = c.req.param("shareId");
|
||||
const space = c.req.param("space") || c.req.query("space") || "default";
|
||||
|
|
@ -506,7 +506,7 @@ routes.post("/api/cards", async (c) => {
|
|||
const authToken = extractToken(c.req.raw.headers);
|
||||
if (!authToken) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json<{ title: string; body?: string; card_type?: string; tags?: string[]; shared_space?: string }>();
|
||||
const space = c.req.param("space") || body.shared_space || "default";
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import * as Automerge from "@automerge/automerge";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import { getTransakEnv, getTransakWebhookSecret } from "../../shared/transak";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
|
|
@ -151,7 +151,7 @@ routes.post("/api/flows", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.text();
|
||||
const res = await fetch(`${FLOW_SERVICE_URL}/api/flows`, {
|
||||
|
|
@ -386,7 +386,7 @@ const ENTRY_POINT = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789'; // v0.6
|
|||
routes.post("/api/flows/submit-userop", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
if (!_pimlico) return c.json({ error: "Pimlico bundler not configured" }, 503);
|
||||
|
||||
|
|
@ -404,7 +404,7 @@ routes.post("/api/flows/submit-userop", async (c) => {
|
|||
routes.post("/api/flows/send-userop", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
if (!_pimlico) return c.json({ error: "Pimlico bundler not configured" }, 503);
|
||||
|
||||
|
|
@ -424,7 +424,7 @@ routes.post("/api/flows/send-userop", async (c) => {
|
|||
routes.get("/api/flows/userop/:hash", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
if (!_pimlico) return c.json({ error: "Pimlico bundler not configured" }, 503);
|
||||
|
||||
|
|
@ -540,7 +540,7 @@ routes.post("/api/space-flows", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const { space, flowId } = await c.req.json();
|
||||
if (!space || !flowId) return c.json({ error: "space and flowId required" }, 400);
|
||||
|
|
@ -560,7 +560,7 @@ routes.delete("/api/space-flows/:flowId", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const flowId = c.req.param("flowId");
|
||||
const space = c.req.query("space") || "";
|
||||
|
|
@ -636,7 +636,7 @@ routes.post("/api/mortgage/positions", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json() as Partial<MortgagePosition>;
|
||||
if (!body.principal || !body.termMonths || !body.interestRate) {
|
||||
|
|
@ -710,7 +710,7 @@ routes.post("/api/budgets/allocate", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const { space, allocations } = await c.req.json() as { space?: string; allocations?: Record<string, number> };
|
||||
if (!allocations) return c.json({ error: "allocations required" }, 400);
|
||||
|
|
@ -745,7 +745,7 @@ routes.post("/api/budgets/segments", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const { space, action, segmentId, name, color } = await c.req.json() as {
|
||||
space?: string; action: 'add' | 'remove'; segmentId?: string; name?: string; color?: string;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { renderShell, renderExternalAppShell } from "../../server/shell";
|
|||
import { getModuleInfoList } from "../../shared/module";
|
||||
import { provisionInstance, destroyInstance } from "./lib/provisioner";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import {
|
||||
|
|
@ -44,7 +44,7 @@ routes.get("/api/instances", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const doc = ensureDoc();
|
||||
const instances = Object.values(doc.instances).filter(
|
||||
|
|
@ -60,7 +60,7 @@ routes.post("/api/instances", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json<{
|
||||
name: string;
|
||||
|
|
@ -126,7 +126,7 @@ routes.get("/api/instances/:id", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const doc = ensureDoc();
|
||||
const instance = doc.instances[c.req.param("id")];
|
||||
|
|
@ -142,7 +142,7 @@ routes.delete("/api/instances/:id", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const doc = ensureDoc();
|
||||
const instance = doc.instances[c.req.param("id")];
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import * as Automerge from "@automerge/automerge";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import {
|
||||
|
|
@ -548,7 +548,7 @@ routes.post("/api/mailboxes", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { slug, name, email, description, visibility = "private", imap_user, imap_password } = body;
|
||||
|
|
@ -710,7 +710,7 @@ routes.post("/api/threads/:id/comments", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const threadId = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
|
@ -756,7 +756,7 @@ routes.post("/api/threads/:id/reply", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const threadId = c.req.param("id");
|
||||
const found = findThreadById(threadId);
|
||||
|
|
@ -805,7 +805,7 @@ routes.post("/api/threads/:id/reply-all", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const threadId = c.req.param("id");
|
||||
const found = findThreadById(threadId);
|
||||
|
|
@ -866,7 +866,7 @@ routes.post("/api/threads/:id/forward", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const threadId = c.req.param("id");
|
||||
const found = findThreadById(threadId);
|
||||
|
|
@ -963,7 +963,7 @@ routes.post("/api/approvals", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { mailbox_slug, thread_id, subject, body_text, to_addresses, cc_addresses, in_reply_to, references: refs, reply_type } = body;
|
||||
|
|
@ -1009,7 +1009,7 @@ routes.post("/api/approvals/:id/sign", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const id = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
|
@ -1083,7 +1083,7 @@ routes.post("/api/personal-inboxes", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { label, email, imap_host, imap_port, imap_user, imap_pass, smtp_host, smtp_port, smtp_user, smtp_pass } = body;
|
||||
|
|
@ -1158,7 +1158,7 @@ routes.get("/api/personal-inboxes", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const inboxes: any[] = [];
|
||||
for (const { doc } of getAllMailboxDocs()) {
|
||||
|
|
@ -1188,7 +1188,7 @@ routes.delete("/api/personal-inboxes/:id", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const inboxId = c.req.param("id");
|
||||
_personalCredentials.delete(inboxId);
|
||||
|
|
@ -1214,7 +1214,7 @@ routes.post("/api/agent-inboxes", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { name, email, personality, auto_reply = false, auto_classify = false, rules = [] } = body;
|
||||
|
|
@ -1287,7 +1287,7 @@ routes.get("/api/agent-inboxes", async (c) => {
|
|||
routes.delete("/api/agent-inboxes/:id", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const agentId = c.req.param("id");
|
||||
|
||||
|
|
@ -1328,7 +1328,7 @@ routes.post("/api/workspaces", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { slug, name, description } = body;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { renderShell } from "../../server/shell";
|
|||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule, SpaceLifecycleContext } from "../../shared/module";
|
||||
import { resolveDataSpace } from "../../shared/scope-resolver";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import { notebookSchema, notebookDocId, connectionsDocId, createNoteItem } from "./schemas";
|
||||
import type { NotebookDoc, NoteItem, ConnectionsDoc } from "./schemas";
|
||||
|
|
@ -285,7 +285,7 @@ routes.post("/api/notebooks", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { title, description, cover_color } = body;
|
||||
|
|
@ -339,7 +339,7 @@ routes.put("/api/notebooks/:id", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const id = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
|
@ -437,7 +437,7 @@ routes.post("/api/notes", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { notebook_id, title, content, content_format, type, url, language, file_url, mime_type, file_size, duration, tags } = body;
|
||||
|
|
@ -641,7 +641,7 @@ routes.post("/api/import/upload", async (c) => {
|
|||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const formData = await c.req.formData();
|
||||
const file = formData.get("file") as File | null;
|
||||
|
|
@ -693,7 +693,7 @@ routes.post("/api/import/files", async (c) => {
|
|||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const formData = await c.req.formData();
|
||||
const notebookId = formData.get("notebookId") as string | null;
|
||||
|
|
@ -745,7 +745,7 @@ routes.post("/api/import/notion", async (c) => {
|
|||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { pageIds, notebookId, recursive } = body;
|
||||
|
|
@ -791,7 +791,7 @@ routes.post("/api/import/google-docs", async (c) => {
|
|||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { docIds, notebookId } = body;
|
||||
|
|
@ -996,7 +996,7 @@ routes.post("/api/export/notion", async (c) => {
|
|||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { notebookId, noteIds, parentId } = body;
|
||||
|
|
@ -1036,7 +1036,7 @@ routes.post("/api/export/google-docs", async (c) => {
|
|||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { notebookId, noteIds, parentId } = body;
|
||||
|
|
@ -1078,7 +1078,7 @@ routes.post("/api/sync/note/:noteId", async (c) => {
|
|||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const noteId = c.req.param("noteId");
|
||||
const found = findNote(dataSpace, noteId);
|
||||
|
|
@ -1131,7 +1131,7 @@ routes.post("/api/sync/notebook/:id", async (c) => {
|
|||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const notebookId = c.req.param("id");
|
||||
const docId = notebookDocId(dataSpace, notebookId);
|
||||
|
|
@ -1191,7 +1191,7 @@ routes.post("/api/sync/upload", async (c) => {
|
|||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const formData = await c.req.formData();
|
||||
const file = formData.get("file") as File | null;
|
||||
|
|
@ -1306,7 +1306,7 @@ const UPLOAD_DIR = "/data/files/generated";
|
|||
routes.post("/api/uploads", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const formData = await c.req.formData();
|
||||
const file = formData.get("file") as File | null;
|
||||
|
|
@ -1351,7 +1351,7 @@ routes.get("/api/uploads/:filename", async (c) => {
|
|||
routes.post("/api/voice/transcribe", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
try {
|
||||
const formData = await c.req.formData();
|
||||
|
|
@ -1372,7 +1372,7 @@ routes.post("/api/voice/transcribe", async (c) => {
|
|||
routes.post("/api/voice/diarize", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
try {
|
||||
const formData = await c.req.formData();
|
||||
|
|
@ -1397,7 +1397,7 @@ const NOTEBOOK_API_URL = process.env.NOTEBOOK_API_URL || "http://open-notebook:5
|
|||
routes.post("/api/notes/summarize", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const { content, model = "gemini-flash", length = "medium" } = await c.req.json<{
|
||||
content: string; model?: string; length?: "short" | "medium" | "long";
|
||||
|
|
@ -1443,7 +1443,7 @@ routes.post("/api/notes/summarize", async (c) => {
|
|||
routes.post("/api/notes/deep-summarize", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const { noteIds, query } = await c.req.json<{ noteIds: string[]; query?: string }>();
|
||||
if (!noteIds?.length) return c.json({ error: "noteIds required" }, 400);
|
||||
|
|
@ -1498,7 +1498,7 @@ routes.post("/api/notes/deep-summarize", async (c) => {
|
|||
routes.post("/api/notes/send-to-notebook", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const { noteId, title, content } = await c.req.json<{
|
||||
noteId: string; title: string; content: string;
|
||||
|
|
@ -1535,7 +1535,7 @@ routes.post("/api/notes/send-to-notebook", async (c) => {
|
|||
routes.post("/api/articles/unlock", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Unauthorized" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const { url } = await c.req.json<{ url: string }>();
|
||||
if (!url) return c.json({ error: "Missing url" }, 400);
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ import {
|
|||
import { DEMO_FEED } from "./lib/types";
|
||||
import { getListmonkConfig, listmonkFetch } from "./lib/listmonk-proxy";
|
||||
import { getPostizConfig, getIntegrations, createPost, createThread } from "./lib/postiz-client";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import type { EncryptIDClaims } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import type { EncryptIDClaims } from "../../server/auth";
|
||||
import { resolveCallerRole, roleAtLeast } from "../../server/spaces";
|
||||
import type { SpaceRoleString } from "../../server/spaces";
|
||||
|
||||
|
|
@ -432,7 +432,7 @@ async function requireNewsletterRole(c: any, minRole: SpaceRoleString): Promise<
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const result = await resolveCallerRole(space, claims);
|
||||
|
|
@ -1951,7 +1951,7 @@ routes.get("/newsletter-list", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (token) {
|
||||
try {
|
||||
const claims = await verifyEncryptIDToken(token);
|
||||
const claims = await verifyToken(token);
|
||||
const result = await resolveCallerRole(space, claims);
|
||||
if (result) role = result.role;
|
||||
} catch { /* keep viewer default */ }
|
||||
|
|
|
|||
|
|
@ -17,10 +17,7 @@ import { renderShell } from "../../server/shell";
|
|||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule, SpaceLifecycleContext } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
import {
|
||||
verifyEncryptIDToken,
|
||||
extractToken,
|
||||
} from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { setupX402FromEnv } from "../../shared/x402/hono-middleware";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import {
|
||||
|
|
@ -300,7 +297,7 @@ routes.post("/api/splats", async (c) => {
|
|||
|
||||
let claims;
|
||||
try {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
} catch {
|
||||
return c.json({ error: "Invalid token" }, 401);
|
||||
}
|
||||
|
|
@ -413,7 +410,7 @@ routes.post("/api/splats/from-media", async (c) => {
|
|||
|
||||
let claims;
|
||||
try {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
} catch {
|
||||
return c.json({ error: "Invalid token" }, 401);
|
||||
}
|
||||
|
|
@ -557,7 +554,7 @@ routes.post("/api/splats/save-generated", async (c) => {
|
|||
|
||||
let claims;
|
||||
try {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
} catch {
|
||||
return c.json({ error: "Invalid token" }, 401);
|
||||
}
|
||||
|
|
@ -646,7 +643,7 @@ routes.get("/api/splats/my-history", async (c) => {
|
|||
|
||||
let claims;
|
||||
try {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
} catch {
|
||||
return c.json({ error: "Invalid token" }, 401);
|
||||
}
|
||||
|
|
@ -670,7 +667,7 @@ routes.delete("/api/splats/:id", async (c) => {
|
|||
|
||||
let claims;
|
||||
try {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
} catch {
|
||||
return c.json({ error: "Invalid token" }, 401);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import * as Automerge from '@automerge/automerge';
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule, SpaceLifecycleContext } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import { boardSchema, boardDocId, createTaskItem } from './schemas';
|
||||
|
|
@ -214,7 +214,7 @@ routes.post("/api/spaces", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { name, description, icon } = body;
|
||||
|
|
@ -322,7 +322,7 @@ routes.post("/api/spaces/:slug/tasks", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const slug = c.req.param("slug");
|
||||
const body = await c.req.json();
|
||||
|
|
@ -367,7 +367,7 @@ routes.patch("/api/tasks/:id", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
let updatedBy: string | null = null;
|
||||
if (token) {
|
||||
try { const claims = await verifyEncryptIDToken(token); updatedBy = claims.sub; } catch {}
|
||||
try { const claims = await verifyToken(token); updatedBy = claims.sub; } catch {}
|
||||
}
|
||||
|
||||
const id = c.req.param("id");
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import * as Automerge from "@automerge/automerge";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import {
|
||||
|
|
@ -96,7 +96,7 @@ routes.post("/api/trips", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { title, description, start_date, end_date, budget_total, budget_currency } = body;
|
||||
|
|
@ -199,7 +199,7 @@ routes.put("/api/trips/:id", async (c) => {
|
|||
routes.post("/api/trips/:id/destinations", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -236,7 +236,7 @@ routes.post("/api/trips/:id/destinations", async (c) => {
|
|||
routes.post("/api/trips/:id/itinerary", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -273,7 +273,7 @@ routes.post("/api/trips/:id/itinerary", async (c) => {
|
|||
routes.post("/api/trips/:id/bookings", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -311,7 +311,7 @@ routes.post("/api/trips/:id/bookings", async (c) => {
|
|||
routes.post("/api/trips/:id/expenses", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -362,7 +362,7 @@ routes.get("/api/trips/:id/packing", async (c) => {
|
|||
routes.post("/api/trips/:id/packing", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { Hono } from "hono";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import { S3Client, ListObjectsV2Command, GetObjectCommand, HeadObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ routes.get("/api/v/*", async (c) => {
|
|||
routes.post("/api/videos", async (c) => {
|
||||
const authToken = extractToken(c.req.raw.headers);
|
||||
if (!authToken) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const client = getS3();
|
||||
if (!client) return c.json({ error: "R2 not configured" }, 503);
|
||||
|
|
@ -198,7 +198,7 @@ routes.get("/api/health", (c) => c.json({ ok: true }));
|
|||
routes.post("/api/360split", async (c) => {
|
||||
const authToken = extractToken(c.req.raw.headers);
|
||||
if (!authToken) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
if (!SPLIT_360_URL) return c.json({ error: "360split service not configured" }, 503);
|
||||
|
||||
|
|
@ -255,7 +255,7 @@ routes.get("/api/360split/status/:jobId", async (c) => {
|
|||
routes.post("/api/360split/import/:jobId", async (c) => {
|
||||
const authToken = extractToken(c.req.raw.headers);
|
||||
if (!authToken) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
if (!SPLIT_360_URL) return c.json({ error: "360split service not configured" }, 503);
|
||||
const client = getS3();
|
||||
|
|
@ -312,7 +312,7 @@ routes.post("/api/360split/import/:jobId", async (c) => {
|
|||
routes.post("/api/live-split", async (c) => {
|
||||
const authToken = extractToken(c.req.raw.headers);
|
||||
if (!authToken) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
if (!SPLIT_360_URL) return c.json({ error: "360split service not configured" }, 503);
|
||||
|
||||
|
|
@ -363,7 +363,7 @@ routes.get("/api/live-split/status/:sessionId", async (c) => {
|
|||
routes.post("/api/live-split/stop/:sessionId", async (c) => {
|
||||
const authToken = extractToken(c.req.raw.headers);
|
||||
if (!authToken) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
if (!SPLIT_360_URL) return c.json({ error: "360split service not configured" }, 503);
|
||||
const sessionId = c.req.param("sessionId");
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import * as Automerge from "@automerge/automerge";
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import { vnbSchema, vnbDocId } from './schemas';
|
||||
|
|
@ -612,7 +612,7 @@ routes.get("/api/vehicles", async (c) => {
|
|||
routes.post("/api/vehicles", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -691,7 +691,7 @@ routes.get("/api/vehicles/:id", async (c) => {
|
|||
routes.patch("/api/vehicles/:id", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -782,7 +782,7 @@ routes.get("/api/vehicles/:id/availability", async (c) => {
|
|||
routes.post("/api/vehicles/:id/availability", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -822,7 +822,7 @@ routes.post("/api/vehicles/:id/availability", async (c) => {
|
|||
routes.patch("/api/availability/:id", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -888,7 +888,7 @@ routes.get("/api/rentals", async (c) => {
|
|||
routes.post("/api/rentals", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -967,7 +967,7 @@ function rentalTransition(statusTarget: RentalStatus, timestampField: 'responded
|
|||
return async (c: any) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -997,7 +997,7 @@ routes.post("/api/rentals/:id/complete", rentalTransition('completed', 'complete
|
|||
routes.post("/api/rentals/:id/messages", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -1047,7 +1047,7 @@ routes.get("/api/endorsements", async (c) => {
|
|||
routes.post("/api/endorsements", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
@ -1250,7 +1250,7 @@ routes.get("/api/config", async (c) => {
|
|||
routes.patch("/api/config", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const space = c.req.param("space") || "demo";
|
||||
const dataSpace = c.get("effectiveSpace") || space;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import * as Automerge from '@automerge/automerge';
|
|||
import { renderShell } from "../../server/shell";
|
||||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
import { renderLanding } from "./landing";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import { proposalSchema, proposalDocId, computeElo, ELO_DEFAULT } from './schemas';
|
||||
|
|
@ -286,7 +286,7 @@ routes.post("/api/spaces", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { name, slug, description, visibility = "public" } = body;
|
||||
|
|
@ -359,7 +359,7 @@ routes.post("/api/proposals", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { space_slug, title, description } = body;
|
||||
|
|
@ -407,7 +407,7 @@ routes.post("/api/proposals/:id/vote", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const id = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
|
@ -470,7 +470,7 @@ routes.post("/api/proposals/:id/final-vote", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const id = c.req.param("id");
|
||||
const body = await c.req.json();
|
||||
|
|
@ -564,7 +564,7 @@ routes.get("/api/proposals/pair", (c) => {
|
|||
routes.post("/api/proposals/compare", async (c) => {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
try { await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json();
|
||||
const { winnerId, loserId } = body;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { renderShell } from "../../server/shell";
|
|||
import { getModuleInfoList } from "../../shared/module";
|
||||
import type { RSpaceModule } from "../../shared/module";
|
||||
import { renderLanding } from "./landing";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../../server/auth";
|
||||
|
||||
const routes = new Hono();
|
||||
|
||||
|
|
@ -271,7 +271,7 @@ async function verifyWalletAuth(c: any): Promise<{ sub: string; did?: string; us
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return null;
|
||||
try {
|
||||
const claims = await verifyEncryptIDToken(token);
|
||||
const claims = await verifyToken(token);
|
||||
return claims as any;
|
||||
} catch {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Auth wrapper — local JWT verification for rSpace server.
|
||||
*
|
||||
* When JWT_SECRET is available, verifies tokens locally via HMAC-SHA256 (<1ms).
|
||||
* Otherwise falls back to internal HTTP call to EncryptID service.
|
||||
* Re-exports extractToken and types for convenience.
|
||||
*/
|
||||
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import type { EncryptIDClaims, VerifyOptions } from "@encryptid/sdk/server";
|
||||
|
||||
export { extractToken };
|
||||
export type { EncryptIDClaims };
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET;
|
||||
const ENCRYPTID_INTERNAL = process.env.ENCRYPTID_INTERNAL_URL || "http://encryptid:3000";
|
||||
|
||||
const verifyOpts: VerifyOptions = JWT_SECRET
|
||||
? { secret: JWT_SECRET }
|
||||
: { serverUrl: ENCRYPTID_INTERNAL };
|
||||
|
||||
export function verifyToken(token: string): Promise<EncryptIDClaims> {
|
||||
return verifyEncryptIDToken(token, verifyOpts);
|
||||
}
|
||||
|
|
@ -1,5 +1,16 @@
|
|||
declare module '@encryptid/sdk/server' {
|
||||
export function verifyEncryptIDToken(token: string): Promise<EncryptIDClaims>;
|
||||
export interface VerifyOptions {
|
||||
/** JWT secret for local HMAC-SHA256 verification */
|
||||
secret?: string;
|
||||
/** EncryptID server URL for remote verification */
|
||||
serverUrl?: string;
|
||||
/** Expected audience */
|
||||
audience?: string;
|
||||
/** Clock tolerance in seconds */
|
||||
clockTolerance?: number;
|
||||
}
|
||||
|
||||
export function verifyEncryptIDToken(token: string, options?: VerifyOptions): Promise<EncryptIDClaims>;
|
||||
export function evaluateSpaceAccess(
|
||||
slug: string,
|
||||
token: string | null,
|
||||
|
|
|
|||
|
|
@ -35,12 +35,12 @@ import { seedTemplateShapes } from "./seed-template";
|
|||
// Campaign demo moved to rsocials module — see modules/rsocials/campaign-data.ts
|
||||
import type { SpaceVisibility } from "./community-store";
|
||||
import {
|
||||
verifyEncryptIDToken,
|
||||
evaluateSpaceAccess,
|
||||
extractToken,
|
||||
authenticateWSUpgrade,
|
||||
} from "@encryptid/sdk/server";
|
||||
import type { EncryptIDClaims, SpaceAuthConfig } from "@encryptid/sdk/server";
|
||||
import type { SpaceAuthConfig } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "./auth";
|
||||
import type { EncryptIDClaims } from "./auth";
|
||||
|
||||
// ── Module system ──
|
||||
import { registerModule, getAllModules, getModuleInfoList, getModule } from "../shared/module";
|
||||
|
|
@ -518,7 +518,7 @@ app.post("/api/communities", async (c) => {
|
|||
|
||||
let claims: EncryptIDClaims;
|
||||
try {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
} catch {
|
||||
return c.json({ error: "Invalid or expired authentication token" }, 401);
|
||||
}
|
||||
|
|
@ -653,7 +653,7 @@ app.get("/api/space-access/:slug", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ access: false, reason: "not-authenticated" });
|
||||
let claims: EncryptIDClaims | null = null;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch {}
|
||||
try { claims = await verifyToken(token); } catch {}
|
||||
if (!claims) return c.json({ access: false, reason: "not-authenticated" });
|
||||
|
||||
const config = await getSpaceConfig(slug);
|
||||
|
|
@ -711,7 +711,7 @@ import { getBalance, getTokenDoc, transferTokens, mintFromOnChain } from "./toke
|
|||
|
||||
// Wire EncryptID JWT verifier into CRDT scheme
|
||||
setTokenVerifier(async (token: string) => {
|
||||
const claims = await verifyEncryptIDToken(token);
|
||||
const claims = await verifyToken(token);
|
||||
return { sub: claims.sub, did: claims.did as string | undefined, username: claims.username };
|
||||
});
|
||||
|
||||
|
|
@ -735,7 +735,7 @@ const x402Test = setupX402FromEnv({
|
|||
console.warn("[x402 bridge] No JWT — skipping cUSDC mint (on-chain payment still valid)");
|
||||
return;
|
||||
}
|
||||
const claims = await verifyEncryptIDToken(token);
|
||||
const claims = await verifyToken(token);
|
||||
const did = (claims.did as string) || `did:key:${(claims.sub as string).slice(0, 32)}`;
|
||||
const label = claims.username || did;
|
||||
const amount = process.env.X402_UPLOAD_PRICE || "0.01";
|
||||
|
|
@ -1619,8 +1619,13 @@ const OLLAMA_MODELS: Record<string, string> = {
|
|||
"mistral-small": "mistral-small:24b",
|
||||
};
|
||||
|
||||
const CANVAS_TOOLS_SYSTEM_PROMPT = `You are a helpful AI assistant in rSpace, a collaborative canvas workspace.
|
||||
When the user asks to create, show, display, or visualize something, use the available tools to spawn shapes on the canvas.
|
||||
After creating shapes, give a brief summary of what you placed. Only create shapes directly relevant to the request.
|
||||
For text-only questions (explanations, coding help, math), respond with text — don't create shapes unless asked.`;
|
||||
|
||||
app.post("/api/prompt", async (c) => {
|
||||
const { messages, model = "gemini-flash" } = await c.req.json();
|
||||
const { messages, model = "gemini-flash", useTools = false, systemPrompt } = await c.req.json();
|
||||
if (!messages?.length) return c.json({ error: "messages required" }, 400);
|
||||
|
||||
// Determine provider
|
||||
|
|
@ -1629,7 +1634,15 @@ app.post("/api/prompt", async (c) => {
|
|||
|
||||
const { GoogleGenerativeAI } = await import("@google/generative-ai");
|
||||
const genAI = new GoogleGenerativeAI(GEMINI_API_KEY);
|
||||
const geminiModel = genAI.getGenerativeModel({ model: GEMINI_MODELS[model] });
|
||||
|
||||
// Build model config with optional tools
|
||||
const modelConfig: any = { model: GEMINI_MODELS[model] };
|
||||
if (useTools) {
|
||||
const { CANVAS_TOOL_DECLARATIONS } = await import("../lib/canvas-tools");
|
||||
modelConfig.tools = [{ functionDeclarations: CANVAS_TOOL_DECLARATIONS }];
|
||||
modelConfig.systemInstruction = systemPrompt || CANVAS_TOOLS_SYSTEM_PROMPT;
|
||||
}
|
||||
const geminiModel = genAI.getGenerativeModel(modelConfig);
|
||||
|
||||
// Convert chat messages to Gemini contents format (with optional images)
|
||||
const contents = messages.map((m: { role: string; content: string; images?: string[] }) => {
|
||||
|
|
@ -1646,9 +1659,54 @@ app.post("/api/prompt", async (c) => {
|
|||
});
|
||||
|
||||
try {
|
||||
const result = await geminiModel.generateContent({ contents });
|
||||
const text = result.response.text();
|
||||
return c.json({ content: text });
|
||||
if (!useTools) {
|
||||
const result = await geminiModel.generateContent({ contents });
|
||||
const text = result.response.text();
|
||||
return c.json({ content: text });
|
||||
}
|
||||
|
||||
// Multi-turn tool loop (max 5 rounds)
|
||||
const toolCalls: { name: string; args: Record<string, any>; label: string }[] = [];
|
||||
const { findTool } = await import("../lib/canvas-tools");
|
||||
let loopContents = [...contents];
|
||||
|
||||
for (let turn = 0; turn < 5; turn++) {
|
||||
const result = await geminiModel.generateContent({ contents: loopContents });
|
||||
const response = result.response;
|
||||
const candidate = response.candidates?.[0];
|
||||
if (!candidate) break;
|
||||
|
||||
const parts = candidate.content?.parts || [];
|
||||
const fnCalls = parts.filter((p: any) => p.functionCall);
|
||||
|
||||
if (fnCalls.length === 0) {
|
||||
// No more tool calls — extract final text
|
||||
const text = response.text();
|
||||
return c.json({ content: text, toolCalls });
|
||||
}
|
||||
|
||||
// Record tool calls and build function responses
|
||||
const fnResponseParts: any[] = [];
|
||||
for (const part of fnCalls) {
|
||||
const fc = part.functionCall;
|
||||
const tool = findTool(fc.name);
|
||||
const label = tool?.actionLabel(fc.args) || fc.name;
|
||||
toolCalls.push({ name: fc.name, args: fc.args, label });
|
||||
fnResponseParts.push({
|
||||
functionResponse: {
|
||||
name: fc.name,
|
||||
response: { success: true, message: `${label} — shape will be created on the canvas.` },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Append model turn + function responses for next iteration
|
||||
loopContents.push({ role: "model", parts });
|
||||
loopContents.push({ role: "user", parts: fnResponseParts });
|
||||
}
|
||||
|
||||
// Exhausted loop — return what we have
|
||||
return c.json({ content: "I've set up the requested items on your canvas.", toolCalls });
|
||||
} catch (e: any) {
|
||||
console.error("[prompt] Gemini error:", e.message);
|
||||
return c.json({ error: "Gemini request failed" }, 502);
|
||||
|
|
@ -1985,7 +2043,7 @@ app.post("/api/spaces/auto-provision", async (c) => {
|
|||
|
||||
let claims: EncryptIDClaims;
|
||||
try {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
} catch {
|
||||
return c.json({ error: "Invalid or expired token" }, 401);
|
||||
}
|
||||
|
|
@ -2111,7 +2169,7 @@ for (const mod of getAllModules()) {
|
|||
return c.json({ error: "Authentication required" }, 401);
|
||||
}
|
||||
let claims: EncryptIDClaims | null = null;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch {}
|
||||
try { claims = await verifyToken(token); } catch {}
|
||||
if (!claims) {
|
||||
return c.json({ error: "Authentication required" }, 401);
|
||||
}
|
||||
|
|
@ -2135,7 +2193,7 @@ for (const mod of getAllModules()) {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
let claims: EncryptIDClaims | null = null;
|
||||
if (token) {
|
||||
try { claims = await verifyEncryptIDToken(token); } catch {}
|
||||
try { claims = await verifyToken(token); } catch {}
|
||||
}
|
||||
const resolved = await resolveCallerRole(space, claims);
|
||||
if (resolved) {
|
||||
|
|
@ -2242,7 +2300,7 @@ app.get("/admin-data", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
if (!isAdminDID(claims.sub)) return c.json({ error: "Admin access required" }, 403);
|
||||
|
||||
const STORAGE_DIR = process.env.STORAGE_DIR || "./data/communities";
|
||||
|
|
@ -2309,7 +2367,7 @@ app.post("/admin-action", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
if (!isAdminDID(claims.sub)) return c.json({ error: "Admin access required" }, 403);
|
||||
|
||||
const body = await c.req.json();
|
||||
|
|
@ -2721,7 +2779,7 @@ const server = Bun.serve<WSData>({
|
|||
if (jwtUsername === subdomain && !provisioningInProgress.has(subdomain)) {
|
||||
provisioningInProgress.add(subdomain);
|
||||
try {
|
||||
const claims = await verifyEncryptIDToken(token);
|
||||
const claims = await verifyToken(token);
|
||||
const username = claims.username?.toLowerCase();
|
||||
if (username === subdomain && !(await communityExists(subdomain))) {
|
||||
await createSpace({
|
||||
|
|
|
|||
|
|
@ -7,11 +7,8 @@
|
|||
|
||||
import { Hono } from "hono";
|
||||
import type { Context, Next } from "hono";
|
||||
import {
|
||||
verifyEncryptIDToken,
|
||||
extractToken,
|
||||
} from "@encryptid/sdk/server";
|
||||
import type { EncryptIDClaims } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "../auth";
|
||||
import type { EncryptIDClaims } from "../auth";
|
||||
import {
|
||||
putBackup,
|
||||
getBackup,
|
||||
|
|
@ -40,7 +37,7 @@ backupRouter.use("*", async (c: Context<BackupEnv>, next: Next) => {
|
|||
}
|
||||
let claims: EncryptIDClaims;
|
||||
try {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
} catch {
|
||||
return c.json({ error: "Invalid or expired token" }, 401);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import type { MiMessage } from "./mi-provider";
|
|||
import { getModuleInfoList, getAllModules } from "../shared/module";
|
||||
import { resolveCallerRole, roleAtLeast } from "./spaces";
|
||||
import type { SpaceRoleString } from "./spaces";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import type { EncryptIDClaims } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "./auth";
|
||||
import type { EncryptIDClaims } from "./auth";
|
||||
import { buildModuleCapabilities, MODULE_ROUTES } from "../lib/mi-module-routes";
|
||||
import type { MiAction } from "../lib/mi-actions";
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ mi.post("/ask", async (c) => {
|
|||
try {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (token) {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
}
|
||||
} catch { /* unauthenticated → viewer */ }
|
||||
|
||||
|
|
@ -369,7 +369,7 @@ mi.post("/validate-actions", async (c) => {
|
|||
try {
|
||||
const token = extractToken(c.req.raw.headers);
|
||||
if (token) {
|
||||
const claims = await verifyEncryptIDToken(token);
|
||||
const claims = await verifyToken(token);
|
||||
if (space && claims) {
|
||||
const resolved = await resolveCallerRole(space, claims);
|
||||
if (resolved) callerRole = resolved.role;
|
||||
|
|
|
|||
|
|
@ -5,10 +5,7 @@
|
|||
*/
|
||||
|
||||
import { Hono } from "hono";
|
||||
import {
|
||||
verifyEncryptIDToken,
|
||||
extractToken,
|
||||
} from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "./auth";
|
||||
import {
|
||||
getUserNotifications,
|
||||
getUnreadCount,
|
||||
|
|
@ -28,7 +25,7 @@ async function requireAuth(req: Request) {
|
|||
const token = extractToken(req.headers);
|
||||
if (!token) return null;
|
||||
try {
|
||||
return await verifyEncryptIDToken(token);
|
||||
return await verifyToken(token);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,11 +59,8 @@ import {
|
|||
invertDirection,
|
||||
computeMembranePermeability,
|
||||
} from "../lib/connection-types";
|
||||
import {
|
||||
verifyEncryptIDToken,
|
||||
extractToken,
|
||||
} from "@encryptid/sdk/server";
|
||||
import type { EncryptIDClaims } from "@encryptid/sdk/server";
|
||||
import { verifyToken, extractToken } from "./auth";
|
||||
import type { EncryptIDClaims } from "./auth";
|
||||
import { getAllModules, getModule } from "../shared/module";
|
||||
import type { SpaceLifecycleContext } from "../shared/module";
|
||||
import { syncServer } from "./sync-instance";
|
||||
|
|
@ -222,7 +219,7 @@ spaces.get("/", async (c) => {
|
|||
let claims: EncryptIDClaims | null = null;
|
||||
if (token) {
|
||||
try {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
} catch {
|
||||
// Invalid token — treat as unauthenticated
|
||||
}
|
||||
|
|
@ -320,7 +317,7 @@ spaces.post("/", async (c) => {
|
|||
|
||||
let claims: EncryptIDClaims;
|
||||
try {
|
||||
claims = await verifyEncryptIDToken(token);
|
||||
claims = await verifyToken(token);
|
||||
} catch {
|
||||
return c.json({ error: "Invalid or expired token" }, 401);
|
||||
}
|
||||
|
|
@ -397,7 +394,7 @@ spaces.patch("/:slug/modules", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const doc = getDocumentData(slug);
|
||||
|
|
@ -484,7 +481,7 @@ spaces.get("/admin", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
if (!isAdmin(claims.sub)) return c.json({ error: "Admin access required" }, 403);
|
||||
|
||||
const STORAGE_DIR = process.env.STORAGE_DIR || "./data/communities";
|
||||
|
|
@ -550,7 +547,7 @@ spaces.delete("/admin/:slug", async (c) => {
|
|||
const token = extractToken(c.req.raw.headers);
|
||||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
if (!isAdmin(claims.sub)) return c.json({ error: "Admin access required" }, 403);
|
||||
|
||||
await loadCommunity(slug);
|
||||
|
|
@ -598,7 +595,7 @@ spaces.get("/notifications", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const slugs = await listCommunities();
|
||||
const ownedSlugs = new Set<string>();
|
||||
|
|
@ -647,7 +644,7 @@ spaces.delete("/:slug", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
// Protect core spaces
|
||||
if (slug === "demo" || slug === "commonshub") {
|
||||
|
|
@ -696,7 +693,7 @@ spaces.patch("/:slug", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -736,7 +733,7 @@ spaces.get("/:slug/members", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -765,7 +762,7 @@ spaces.patch("/:slug/members/:did", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -815,7 +812,7 @@ spaces.delete("/:slug/members/:did", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -856,7 +853,7 @@ spaces.get("/:slug/access-requests", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -896,7 +893,7 @@ spaces.patch("/:slug/nest-policy", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -935,7 +932,7 @@ spaces.post("/:slug/nest", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json<{
|
||||
sourceSlug: string;
|
||||
|
|
@ -1072,7 +1069,7 @@ spaces.patch("/:slug/nest/:refId", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1104,7 +1101,7 @@ spaces.delete("/:slug/nest/:refId", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1146,7 +1143,7 @@ spaces.get("/:slug/nested-in", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
// Must be admin/owner of the space to see where it's nested
|
||||
await loadCommunity(slug);
|
||||
|
|
@ -1175,7 +1172,7 @@ spaces.get("/:slug/nest-requests", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1203,7 +1200,7 @@ spaces.patch("/:slug/nest-requests/:reqId", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1290,7 +1287,7 @@ spaces.patch("/:slug/connection-policy", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1316,7 +1313,7 @@ spaces.get("/:slug/connections", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1341,7 +1338,7 @@ spaces.post("/:slug/connections", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json<{
|
||||
toSlug: string;
|
||||
|
|
@ -1496,7 +1493,7 @@ spaces.get("/:slug/connections/:connId", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1523,7 +1520,7 @@ spaces.patch("/:slug/connections/:connId", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1592,7 +1589,7 @@ spaces.delete("/:slug/connections/:connId", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1641,7 +1638,7 @@ spaces.get("/:slug/connection-requests", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1669,7 +1666,7 @@ spaces.patch("/:slug/connection-requests/:reqId", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1759,7 +1756,7 @@ spaces.get("/:slug/membrane", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1803,7 +1800,7 @@ spaces.patch("/:slug/encryption", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1843,7 +1840,7 @@ spaces.post("/:slug/access-requests", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1904,7 +1901,7 @@ spaces.patch("/:slug/access-requests/:reqId", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -1985,7 +1982,7 @@ spaces.post("/:slug/copy-shapes", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
// Verify target space exists and user has write access
|
||||
await loadCommunity(targetSlug);
|
||||
|
|
@ -2098,7 +2095,7 @@ spaces.post("/:slug/invite", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -2218,7 +2215,7 @@ spaces.post("/:slug/members/add", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
@ -2325,7 +2322,7 @@ spaces.post("/:slug/invite/accept", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required — sign in first" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
const body = await c.req.json<{ inviteToken: string }>();
|
||||
if (!body.inviteToken) return c.json({ error: "inviteToken is required" }, 400);
|
||||
|
|
@ -2359,7 +2356,7 @@ spaces.get("/:slug/invites", async (c) => {
|
|||
if (!token) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
let claims: EncryptIDClaims;
|
||||
try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }
|
||||
|
||||
await loadCommunity(slug);
|
||||
const data = getDocumentData(slug);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export class RStackSpaceSettings extends HTMLElement {
|
|||
private _addMode: "username" | "email" = "username";
|
||||
private _lookupResult: { did: string; username: string; displayName: string } | null = null;
|
||||
private _lookupError = "";
|
||||
private _searchResults: { id: string; did: string; username: string; displayName: string }[] = [];
|
||||
private _moduleId = "";
|
||||
private _moduleConfig: ModuleConfig | null = null;
|
||||
private _moduleSettingsValues: Record<string, string | boolean> = {};
|
||||
|
|
@ -167,20 +168,25 @@ export class RStackSpaceSettings extends HTMLElement {
|
|||
} catch {}
|
||||
}
|
||||
|
||||
// Resolve missing displayNames from EncryptID
|
||||
const unresolvedDids = this._members.filter(m => !m.displayName).map(m => m.did);
|
||||
if (unresolvedDids.length) {
|
||||
// Resolve displayNames from EncryptID for all members + owner
|
||||
const allDids = [...new Set([
|
||||
...this._members.map(m => m.did),
|
||||
...(this._ownerDID ? [this._ownerDID] : []),
|
||||
])];
|
||||
if (allDids.length) {
|
||||
try {
|
||||
const res = await fetch("/api/users/resolve-dids", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ dids: unresolvedDids }),
|
||||
body: JSON.stringify({ dids: allDids }),
|
||||
});
|
||||
if (res.ok) {
|
||||
const resolved = await res.json() as Record<string, { username: string; displayName: string }>;
|
||||
for (const m of this._members) {
|
||||
if (!m.displayName && resolved[m.did]) {
|
||||
m.displayName = resolved[m.did].displayName || resolved[m.did].username;
|
||||
// Match by did or by id (members may be stored as userId)
|
||||
const info = resolved[m.did];
|
||||
if (info) {
|
||||
m.displayName = info.displayName || info.username;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -291,7 +297,7 @@ export class RStackSpaceSettings extends HTMLElement {
|
|||
|
||||
const membersHTML = this._members.map(m => {
|
||||
const isOwnerRow = m.did === this._ownerDID;
|
||||
const displayName = m.displayName || m.did.slice(0, 16) + "…";
|
||||
const displayName = m.displayName || "Unknown user";
|
||||
const initial = displayName.charAt(0).toUpperCase();
|
||||
const roleBadge = isOwnerRow
|
||||
? `<span class="role-badge role-owner">Owner</span>`
|
||||
|
|
@ -404,8 +410,20 @@ export class RStackSpaceSettings extends HTMLElement {
|
|||
|
||||
${this._addMode === "username" ? `
|
||||
<div class="add-form">
|
||||
<input type="text" class="input" id="add-username" placeholder="Username…" />
|
||||
${this._lookupResult ? `<div class="lookup-result">Found: <strong>${this._esc(this._lookupResult.displayName)}</strong> (@${this._esc(this._lookupResult.username)})</div>` : ""}
|
||||
<div class="search-wrapper">
|
||||
<input type="text" class="input" id="add-username" placeholder="Search username…" autocomplete="off" />
|
||||
${this._searchResults.length > 0 ? `
|
||||
<div class="search-dropdown">
|
||||
${this._searchResults.map((u, i) => `
|
||||
<div class="search-item" data-idx="${i}">
|
||||
<span class="search-item-name">${this._esc(u.displayName)}</span>
|
||||
<span class="search-item-username">@${this._esc(u.username)}</span>
|
||||
</div>
|
||||
`).join("")}
|
||||
</div>
|
||||
` : ""}
|
||||
</div>
|
||||
${this._lookupResult ? `<div class="lookup-result">Selected: <strong>${this._esc(this._lookupResult.displayName)}</strong> (@${this._esc(this._lookupResult.username)})</div>` : ""}
|
||||
${this._lookupError ? `<div class="error-msg">${this._esc(this._lookupError)}</div>` : ""}
|
||||
<div class="add-row">
|
||||
<select class="input role-input" id="add-role">
|
||||
|
|
@ -414,7 +432,7 @@ export class RStackSpaceSettings extends HTMLElement {
|
|||
<option value="moderator">moderator</option>
|
||||
<option value="admin">admin</option>
|
||||
</select>
|
||||
<button class="add-btn" id="add-by-username">Add</button>
|
||||
<button class="add-btn" id="add-by-username" ${!this._lookupResult ? "disabled" : ""}>Add</button>
|
||||
</div>
|
||||
</div>
|
||||
` : `
|
||||
|
|
@ -473,6 +491,14 @@ export class RStackSpaceSettings extends HTMLElement {
|
|||
});
|
||||
}
|
||||
|
||||
// Search result selection
|
||||
sr.querySelectorAll(".search-item").forEach(item => {
|
||||
item.addEventListener("click", () => {
|
||||
const idx = parseInt((item as HTMLElement).dataset.idx || "0");
|
||||
if (this._searchResults[idx]) this._selectSearchResult(this._searchResults[idx]);
|
||||
});
|
||||
});
|
||||
|
||||
// Add by username
|
||||
sr.getElementById("add-by-username")?.addEventListener("click", () => this._addByUsername());
|
||||
|
||||
|
|
@ -522,27 +548,48 @@ export class RStackSpaceSettings extends HTMLElement {
|
|||
}
|
||||
|
||||
private async _lookupUser(username: string) {
|
||||
if (!username || username.length < 2) return;
|
||||
if (!username || username.length < 2) {
|
||||
this._searchResults = [];
|
||||
this._lookupResult = null;
|
||||
this._lookupError = "";
|
||||
this._render();
|
||||
return;
|
||||
}
|
||||
const token = getToken();
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`https://auth.rspace.online/api/users/lookup?username=${encodeURIComponent(username)}`, {
|
||||
const res = await fetch(`/api/users/search?q=${encodeURIComponent(username)}&limit=5`, {
|
||||
headers: { "Authorization": `Bearer ${token}` },
|
||||
});
|
||||
if (res.ok) {
|
||||
this._lookupResult = await res.json();
|
||||
this._lookupError = "";
|
||||
this._searchResults = await res.json();
|
||||
this._lookupError = this._searchResults.length === 0 && username.length > 2 ? "No users found" : "";
|
||||
// Auto-select if exact match
|
||||
const exact = this._searchResults.find(u => u.username.toLowerCase() === username.toLowerCase());
|
||||
this._lookupResult = exact ? { did: exact.id, username: exact.username, displayName: exact.displayName } : null;
|
||||
} else {
|
||||
this._searchResults = [];
|
||||
this._lookupResult = null;
|
||||
this._lookupError = username.length > 2 ? "User not found" : "";
|
||||
this._lookupError = username.length > 2 ? "No users found" : "";
|
||||
}
|
||||
} catch {
|
||||
this._lookupError = "Lookup failed";
|
||||
this._searchResults = [];
|
||||
}
|
||||
this._render();
|
||||
}
|
||||
|
||||
private _selectSearchResult(user: { id: string; did: string; username: string; displayName: string }) {
|
||||
this._lookupResult = { did: user.id, username: user.username, displayName: user.displayName };
|
||||
this._searchResults = [];
|
||||
this._lookupError = "";
|
||||
this._render();
|
||||
// Set the input value to the selected username
|
||||
const input = this.shadowRoot?.getElementById("add-username") as HTMLInputElement;
|
||||
if (input) input.value = user.username;
|
||||
}
|
||||
|
||||
private async _addByUsername() {
|
||||
const sr = this.shadowRoot!;
|
||||
const input = sr.getElementById("add-username") as HTMLInputElement;
|
||||
|
|
@ -914,12 +961,58 @@ const PANEL_CSS = `
|
|||
}
|
||||
.add-btn:hover { opacity: 0.9; }
|
||||
|
||||
.search-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--rs-bg-surface);
|
||||
border: 1px solid var(--rs-border);
|
||||
border-radius: 8px;
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
z-index: 10;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.25);
|
||||
}
|
||||
|
||||
.search-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
transition: background 0.1s;
|
||||
}
|
||||
.search-item:hover {
|
||||
background: var(--rs-bg-hover);
|
||||
}
|
||||
|
||||
.search-item-name {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
color: var(--rs-text-primary);
|
||||
}
|
||||
|
||||
.search-item-username {
|
||||
font-size: 0.72rem;
|
||||
color: var(--rs-text-muted);
|
||||
}
|
||||
|
||||
.lookup-result {
|
||||
font-size: 0.78rem;
|
||||
color: #14b8a6;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.add-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
font-size: 0.78rem;
|
||||
color: #f87171;
|
||||
|
|
|
|||
|
|
@ -3819,6 +3819,35 @@ app.get('/api/users/lookup', async (c) => {
|
|||
});
|
||||
});
|
||||
|
||||
// GET /api/users/search?q=<partial>&limit=5 — fuzzy username/display_name search
|
||||
app.get('/api/users/search', async (c) => {
|
||||
const claims = await verifyTokenFromRequest(c.req.header('Authorization'));
|
||||
if (!claims) return c.json({ error: 'Authentication required' }, 401);
|
||||
|
||||
const q = (c.req.query('q') || '').trim();
|
||||
if (q.length < 2) return c.json({ error: 'Query must be at least 2 characters' }, 400);
|
||||
|
||||
const limit = Math.min(Math.max(parseInt(c.req.query('limit') || '5'), 1), 10);
|
||||
const pattern = `${q}%`;
|
||||
|
||||
const rows = await sql`
|
||||
SELECT id, did, username, display_name
|
||||
FROM users
|
||||
WHERE username ILIKE ${pattern} OR display_name ILIKE ${pattern}
|
||||
ORDER BY
|
||||
CASE WHEN username ILIKE ${q} THEN 0 ELSE 1 END,
|
||||
username
|
||||
LIMIT ${limit}
|
||||
`;
|
||||
|
||||
return c.json(rows.map((r: any) => ({
|
||||
id: r.id,
|
||||
did: r.did,
|
||||
username: r.username,
|
||||
displayName: r.display_name || r.username,
|
||||
})));
|
||||
});
|
||||
|
||||
// POST /api/users/resolve-dids — batch-resolve DIDs/userIds to usernames (public profile data)
|
||||
app.post('/api/users/resolve-dids', async (c) => {
|
||||
const body = await c.req.json();
|
||||
|
|
|
|||
Loading…
Reference in New Issue