ref: working on connected accounts

This commit is contained in:
Steve Korshakov 2025-09-01 15:32:10 -07:00
parent 02949fa615
commit ee48abb737
15 changed files with 112 additions and 17 deletions

View File

@ -2,7 +2,7 @@ import fastify from "fastify";
import { log, logger } from "@/utils/log";
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "fastify-type-provider-zod";
import { onShutdown } from "@/utils/shutdown";
import { EventRouter } from "@/modules/eventRouter";
import { EventRouter } from "@/app/events/eventRouter";
import { Fastify } from "./types";
import { authRoutes } from "./routes/authRoutes";
import { pushRoutes } from "./routes/pushRoutes";

View File

@ -1,4 +1,4 @@
import { EventRouter, buildUpdateAccountUpdate } from "@/modules/eventRouter";
import { EventRouter, buildUpdateAccountUpdate } from "@/app/events/eventRouter";
import { db } from "@/storage/db";
import { Fastify } from "../types";
import { getPublicUrl } from "@/storage/files";
@ -6,6 +6,7 @@ import { z } from "zod";
import { randomKeyNaked } from "@/utils/randomKeyNaked";
import { allocateUserSeq } from "@/storage/seq";
import { log } from "@/utils/log";
import { AccountProfile } from "@/types";
export function accountRoutes(app: Fastify, eventRouter: EventRouter) {
app.get('/v1/account/profile', {
@ -21,13 +22,15 @@ export function accountRoutes(app: Fastify, eventRouter: EventRouter) {
githubUser: true
}
});
const connectedVendors = new Set((await db.serviceAccountToken.findMany({ where: { accountId: userId } })).map(t => t.vendor));
return reply.send({
id: userId,
timestamp: Date.now(),
firstName: user.firstName,
lastName: user.lastName,
avatar: user.avatar ? { ...user.avatar, url: getPublicUrl(user.avatar.path) } : null,
github: user.githubUser ? user.githubUser.profile : null
github: user.githubUser ? user.githubUser.profile : null,
connectedServices: Array.from(connectedVendors)
});
});

View File

@ -6,12 +6,12 @@ import { db } from "@/storage/db";
import { Prisma } from "@prisma/client";
import { allocateUserSeq } from "@/storage/seq";
import { randomKeyNaked } from "@/utils/randomKeyNaked";
import { buildUpdateAccountUpdate } from "@/modules/eventRouter";
import { buildUpdateAccountUpdate } from "@/app/events/eventRouter";
import { GitHubProfile } from "../types";
import { separateName } from "@/utils/separateName";
import { uploadImage } from "@/storage/uploadImage";
import { EventRouter } from "@/modules/eventRouter";
import { encryptString } from "@/modules/encrypt";
import { EventRouter } from "@/app/events/eventRouter";
import { decryptString, encryptString } from "@/modules/encrypt";
export function connectRoutes(app: Fastify, eventRouter: EventRouter) {
@ -359,4 +359,95 @@ export function connectRoutes(app: Fastify, eventRouter: EventRouter) {
return reply.code(500).send({ error: 'Failed to disconnect GitHub account' });
}
});
//
// Inference endpoints
//
app.post('/v1/connect/:vendor/register', {
preHandler: app.authenticate,
schema: {
body: z.object({
token: z.string()
}),
params: z.object({
vendor: z.enum(['openai', 'anthropic', 'gemini'])
})
}
}, async (request, reply) => {
const userId = request.userId;
const encrypted = encryptString(['user', userId, 'vendors', request.params.vendor, 'token'], request.body.token);
await db.serviceAccountToken.upsert({
where: { accountId_vendor: { accountId: userId, vendor: request.params.vendor } },
update: { updatedAt: new Date(), token: encrypted },
create: { accountId: userId, vendor: request.params.vendor, token: encrypted }
});
reply.send({ success: true });
});
app.get('/v1/connect/:vendor/token', {
preHandler: app.authenticate,
schema: {
params: z.object({
vendor: z.enum(['openai', 'anthropic', 'gemini'])
}),
response: {
200: z.object({
token: z.string().nullable()
})
}
}
}, async (request, reply) => {
const userId = request.userId;
const token = await db.serviceAccountToken.findUnique({
where: { accountId_vendor: { accountId: userId, vendor: request.params.vendor } },
select: { token: true }
});
if (!token) {
return reply.send({ token: null });
} else {
return reply.send({ token: decryptString(['user', userId, 'vendors', request.params.vendor, 'token'], token.token) });
}
});
app.delete('/v1/connect/:vendor', {
preHandler: app.authenticate,
schema: {
params: z.object({
vendor: z.enum(['openai', 'anthropic', 'gemini'])
}),
response: {
200: z.object({
success: z.literal(true)
})
}
}
}, async (request, reply) => {
const userId = request.userId;
await db.serviceAccountToken.delete({ where: { accountId_vendor: { accountId: userId, vendor: request.params.vendor } } });
reply.send({ success: true });
});
app.get('/v1/connect/tokens', {
preHandler: app.authenticate,
schema: {
response: {
200: z.object({
tokens: z.array(z.object({
vendor: z.string(),
token: z.string()
}))
})
}
}
}, async (request, reply) => {
const userId = request.userId;
const tokens = await db.serviceAccountToken.findMany({ where: { accountId: userId } });
let decrypted = [];
for (const token of tokens) {
decrypted.push({ vendor: token.vendor, token: decryptString(['user', userId, 'vendors', token.vendor, 'token'], token.token) });
}
return reply.send({ tokens: decrypted });
});
}

View File

@ -1,11 +1,11 @@
import { EventRouter } from "@/modules/eventRouter";
import { EventRouter } from "@/app/events/eventRouter";
import { Fastify } from "../types";
import { z } from "zod";
import { db } from "@/storage/db";
import { log } from "@/utils/log";
import { randomKeyNaked } from "@/utils/randomKeyNaked";
import { allocateUserSeq } from "@/storage/seq";
import { buildUpdateMachineUpdate } from "@/modules/eventRouter";
import { buildUpdateMachineUpdate } from "@/app/events/eventRouter";
export function machinesRoutes(app: Fastify, eventRouter: EventRouter) {
app.post('/v1/machines', {

View File

@ -1,4 +1,4 @@
import { EventRouter, buildNewSessionUpdate } from "@/modules/eventRouter";
import { EventRouter, buildNewSessionUpdate } from "@/app/events/eventRouter";
import { type Fastify } from "../types";
import { db } from "@/storage/db";
import { z } from "zod";

View File

@ -1,6 +1,6 @@
import { onShutdown } from "@/utils/shutdown";
import { Fastify } from "./types";
import { buildMachineActivityEphemeral, ClientConnection, EventRouter } from "@/modules/eventRouter";
import { buildMachineActivityEphemeral, ClientConnection, EventRouter } from "@/app/events/eventRouter";
import { Server, Socket } from "socket.io";
import { log } from "@/utils/log";
import { auth } from "@/app/auth/auth";

View File

@ -1,6 +1,6 @@
import { machineAliveEventsCounter, websocketEventsCounter } from "@/app/monitoring/metrics2";
import { activityCache } from "@/app/presence/sessionCache";
import { buildMachineActivityEphemeral, buildUpdateMachineUpdate, EventRouter } from "@/modules/eventRouter";
import { buildMachineActivityEphemeral, buildUpdateMachineUpdate, EventRouter } from "@/app/events/eventRouter";
import { log } from "@/utils/log";
import { db } from "@/storage/db";
import { Socket } from "socket.io";

View File

@ -1,4 +1,4 @@
import { EventRouter } from "@/modules/eventRouter";
import { EventRouter } from "@/app/events/eventRouter";
import { log } from "@/utils/log";
import { Socket } from "socket.io";

View File

@ -1,6 +1,6 @@
import { sessionAliveEventsCounter, websocketEventsCounter } from "@/app/monitoring/metrics2";
import { activityCache } from "@/app/presence/sessionCache";
import { buildNewMessageUpdate, buildSessionActivityEphemeral, buildUpdateSessionUpdate, ClientConnection, EventRouter } from "@/modules/eventRouter";
import { buildNewMessageUpdate, buildSessionActivityEphemeral, buildUpdateSessionUpdate, ClientConnection, EventRouter } from "@/app/events/eventRouter";
import { db } from "@/storage/db";
import { allocateSessionSeq, allocateUserSeq } from "@/storage/seq";
import { AsyncLock } from "@/utils/lock";

View File

@ -1,7 +1,7 @@
import { Socket } from "socket.io";
import { AsyncLock } from "@/utils/lock";
import { db } from "@/storage/db";
import { buildUsageEphemeral, EventRouter } from "@/modules/eventRouter";
import { buildUsageEphemeral, EventRouter } from "@/app/events/eventRouter";
import { log } from "@/utils/log";
export function usageHandler(userId: string, socket: Socket, eventRouter: EventRouter) {

View File

@ -2,7 +2,7 @@ import { db } from "@/storage/db";
import { delay } from "@/utils/delay";
import { forever } from "@/utils/forever";
import { shutdownSignal } from "@/utils/shutdown";
import { buildMachineActivityEphemeral, buildSessionActivityEphemeral, EventRouter } from "@/modules/eventRouter";
import { buildMachineActivityEphemeral, buildSessionActivityEphemeral, EventRouter } from "@/app/events/eventRouter";
export function startTimeout(eventRouter: EventRouter) {
forever('session-timeout', async () => {

View File

@ -11,7 +11,7 @@ import { startDatabaseMetricsUpdater } from "@/app/monitoring/metrics2";
import { initEncrypt } from "./modules/encrypt";
import { initGithub } from "./modules/github";
import { loadFiles } from "./storage/files";
import { EventRouter } from "./modules/eventRouter";
import { EventRouter } from "./app/events/eventRouter";
async function main() {

View File

@ -18,7 +18,7 @@ export function encryptBytes(path: string[], bytes: Uint8Array) {
}
export function decryptString(path: string[], encrypted: Uint8Array) {
return keyTree!.symmetricDecryptBuffer(path, encrypted);
return keyTree!.symmetricDecryptString(path, encrypted);
}
export function decryptBytes(path: string[], encrypted: Uint8Array) {

View File

@ -10,4 +10,5 @@ export type AccountProfile = {
value: string | null;
version: number;
} | null;
connectedServices: string[];
}