-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of https://github.com/Codehagen/propdock
- Loading branch information
Showing
52 changed files
with
2,538 additions
and
147 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { verifyKey } from '@unkey/api'; | ||
const DEBUG = false; // NB! Change to false before committing. | ||
const FE_KEY = "super-secret"; // TODO: get from wrangler.toml | ||
export async function verifyApiKey(key, dummy = false) { | ||
if (DEBUG) { | ||
console.debug("Middleware debug - processing key:", key); | ||
} | ||
if (dummy) { | ||
const res = false; | ||
if (DEBUG) { | ||
console.debug("Middleware debug - returning API verified as:", res); | ||
} | ||
return res; | ||
} | ||
const { result, error } = await verifyKey(key); | ||
if (DEBUG) { | ||
console.debug("Middleware debug - verify-key results:", result?.code, result?.valid); | ||
} | ||
if (result) { | ||
return result.valid; | ||
} | ||
else if (error) { | ||
console.error("Couldn't verify key:", error.code, error.message); | ||
} | ||
return false; | ||
} | ||
export async function verifyFrontend(pass) { | ||
if (DEBUG) { | ||
console.debug("Middleware debug - processing pass:", pass); | ||
} | ||
const res = (pass == FE_KEY); | ||
if (DEBUG) { | ||
console.debug("Middleware debug - returning API verified as:", res); | ||
} | ||
return res; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// const UNKEY_ROOT = process.env.UNKEY_ROOT | ||
// const API_ID = process.env.UNKEY_API_ID | ||
async function createAPIKey(rk, aId, workspaceId, serviceName = "", prefix = "") { | ||
const options = { | ||
method: 'POST', | ||
headers: { Authorization: `Bearer ${rk}`, 'Content-Type': 'application/json' }, | ||
body: ` | ||
{"apiId":${aId}, | ||
"prefix":${prefix}, | ||
"ownerId":${workspaceId}, | ||
"name":${serviceName}, | ||
"meta":{ | ||
"ratelimit":{ | ||
"type":"fast", | ||
"limit":10, | ||
"duration":60000 | ||
}, | ||
"enabled":true, | ||
}` | ||
}; | ||
try { | ||
const res = await fetch('https://api.unkey.dev/v1/keys.createKey', options); | ||
const result = await res.json(); | ||
console.debug(`Tried to create API key for workspace <${workspaceId}>, returned with status <${res.status}>`); | ||
return result.key; | ||
} | ||
catch (error) { | ||
console.error(`${error.code}: ${error.message}`); | ||
return null; | ||
} | ||
} | ||
export { createAPIKey }; | ||
{ /* | ||
body: ` | ||
{"apiId":${API_ID}, | ||
"prefix":"<string>", | ||
"name":"my key", | ||
"byteLength":135, | ||
"ownerId":"team_123", | ||
"meta":{ | ||
"billingTier":"PRO", | ||
"trialEnds":"2023-06-16T17:16:37.161Z"}, | ||
"roles":[ | ||
"admin", | ||
"finance" | ||
], | ||
"permissions":[ | ||
"domains.create_record", | ||
"say_hello" | ||
], | ||
"expires":1623869797161, | ||
"remaining":1000, | ||
"refill":{ | ||
"interval":"daily", | ||
"amount":100 | ||
}, | ||
"ratelimit":{ | ||
"type":"fast", | ||
"limit":10, | ||
"duration":60000 | ||
}, | ||
"enabled":true, | ||
"environment":"<string>" | ||
}` | ||
*/ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* eslint-disable @typescript-eslint/consistent-type-definitions */ | ||
import { z } from "zod"; | ||
export const zEnv = z.object({ | ||
DATABASE_URL: z.string(), | ||
ENVIRONMENT: z | ||
.enum(["development", "preview", "production"]) | ||
.default("development"), | ||
PO_ROOT: z.string(), | ||
PO_SUB_KEY: z.string(), | ||
PO_APP_KEY: z.string(), | ||
PO_ONBOARD_REDIRECT: z.string(), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { zEnv } from "./env"; | ||
import internal from "./routes/internal"; | ||
import external from "./routes/external"; | ||
import { honoFactory } from "./lib/hono"; | ||
const app = honoFactory(); | ||
// Main-level routes | ||
app.route("/api/external", external); | ||
app.route("/api/internal", internal); | ||
export default { | ||
fetch: (req, env, exCtx) => { | ||
const parsedEnv = zEnv.safeParse(env); | ||
if (!parsedEnv.success) { | ||
return Response.json({ | ||
code: "BAD_ENVIRONMENT", | ||
message: "Some environment variables are missing or are invalid", | ||
errors: parsedEnv.error, | ||
}, { status: 500 }); | ||
} | ||
return app.fetch(req, parsedEnv.data, exCtx); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { PrismaClient, PrismaNeon, Pool } from "@dingify/db"; | ||
const pool = (env) => new Pool({ connectionString: env.DATABASE_URL }); | ||
const adapter = (env) => new PrismaNeon(pool(env)); | ||
const createPrismaClient = (env) => { | ||
// Check if prisma client is already instantiated in global context | ||
const globalPrisma = globalThis; | ||
const existingPrismaClient = globalPrisma.prisma; | ||
if (existingPrismaClient) { | ||
return existingPrismaClient; | ||
} | ||
const prismaClient = new PrismaClient({ | ||
adapter: adapter(env), | ||
log: env.ENVIRONMENT === "development" | ||
? ["error", "warn"] | ||
: ["error"], | ||
errorFormat: "pretty", | ||
}); | ||
if (env.ENVIRONMENT !== "production") { | ||
globalPrisma.prisma = prismaClient; | ||
} | ||
return prismaClient; | ||
}; | ||
export const prisma = (env) => createPrismaClient(env); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { Prisma } from '@prisma/client'; | ||
/** | ||
* Extends Prisma with a filter on all queries such that the result will return only | ||
* instances belonging to the same workspace as the user. | ||
* | ||
* Requires `user` to have a workspace, and the query must target a model that has | ||
* `workspaceId` in its schema. The query must additionally not have `workspaceId` | ||
* in its `where`-clause already. | ||
*/ | ||
function workspaceExtension(user) { | ||
const workspaceId = user.workspaceId; | ||
const ext = Prisma.defineExtension({ | ||
query: { | ||
$allOperations({ model, operation, args, query }) { | ||
args.where = { ...args.where, workspaceId: workspaceId }; | ||
return query(args); | ||
} | ||
} | ||
}); | ||
return ext; | ||
} | ||
export { workspaceExtension }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export function generateApiKey() { | ||
const array = new Uint8Array(32); | ||
crypto.getRandomValues(array); | ||
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(""); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Hono } from "hono"; | ||
function honoFactory() { | ||
const app = new Hono(); | ||
return app; | ||
} | ||
export { honoFactory }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { prisma } from "../lib/db"; | ||
async function getUserApiKeyFull(env, userId, serviceName) { | ||
const db = prisma(env); | ||
let apiKey; | ||
try { | ||
apiKey = await db.userApiKey.findFirst({ | ||
where: { | ||
userId: userId, | ||
serviceName: serviceName, | ||
} | ||
}); | ||
} | ||
catch (error) { | ||
console.log("Error fetching user API key:", error); | ||
return null; | ||
} | ||
return apiKey; | ||
} | ||
async function getUserApiKey(env, userId, serviceName) { | ||
const fullKey = await getUserApiKeyFull(env, userId, serviceName); | ||
let res = null; | ||
if (fullKey) { | ||
res = fullKey.secret; | ||
} | ||
return res; | ||
} | ||
async function getWSApiKeyFull(env, workspaceId, serviceName) { | ||
const db = prisma(env); | ||
let apiKey; | ||
try { | ||
apiKey = await db.wSApiKey.findFirst({ | ||
where: { | ||
workspaceId: workspaceId, | ||
serviceName: serviceName, | ||
} | ||
}); | ||
} | ||
catch (error) { | ||
console.log("Error fetching user API key:", error); | ||
return null; | ||
} | ||
return apiKey; | ||
} | ||
async function getWorkspaceApiKey(env, workspaceId, serviceName) { | ||
const fullKey = await getWSApiKeyFull(env, workspaceId, serviceName); | ||
let res = null; | ||
if (fullKey) { | ||
res = fullKey.secret; | ||
} | ||
return res; | ||
} | ||
async function storeWorkspaceAccessToken(db, workspaceId, serviceName, token, expiry) { | ||
const now = new Date(); | ||
const expiryTime = (expiry * 1000) - 5000; // Shave 5 seconds off of the duration to compensate for roundtrip + db latency | ||
const saveTime = new Date(now.getTime() + expiryTime); | ||
try { | ||
await db.workspaceAccessToken.upsert({ | ||
where: { | ||
workspaceId_serviceName: { | ||
workspaceId: workspaceId, | ||
serviceName: serviceName, | ||
} | ||
}, | ||
update: { | ||
secret: token, | ||
validTo: saveTime, | ||
}, | ||
create: { | ||
workspaceId: workspaceId, | ||
serviceName: serviceName, | ||
secret: token, | ||
validTo: saveTime, | ||
} | ||
}); | ||
} | ||
catch (error) { | ||
console.log(`Error saving access token (${serviceName}:${workspaceId}):`, error); | ||
} | ||
} | ||
async function getWorkspaceAccessToken(db, workspaceId, serviceName) { | ||
let accessToken = await db.workspaceAccessToken.findFirst({ | ||
where: { | ||
workspaceId: workspaceId, | ||
serviceName: serviceName, | ||
} | ||
}); | ||
if (!accessToken) { | ||
console.debug("Returning null"); | ||
return null; | ||
} | ||
const now = new Date(); | ||
const adjustedTime = new Date(now.getTime() + 5000); | ||
if (accessToken.validTo > adjustedTime) { | ||
return accessToken; | ||
} | ||
return null; | ||
} | ||
export { getUserApiKey, getWorkspaceApiKey, getWorkspaceAccessToken, storeWorkspaceAccessToken, }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export function parsePrismaError(error) { | ||
// A simple error message parser that looks for missing arguments | ||
const missingArgumentMatch = error.message.match(/Argument `(\w+)` is missing./); | ||
if (missingArgumentMatch) { | ||
let fieldName = missingArgumentMatch[1]; | ||
let baseMessage = `The '${fieldName}' field is required but was not provided.`; | ||
// Specific instructions for known fields | ||
if (fieldName === "channel") { | ||
baseMessage += " You need to add 'channel' to your call to make it work."; | ||
} | ||
// Add more specific messages for other fields if necessary | ||
return baseMessage; | ||
} | ||
// Default to returning the original error message if no known patterns are matched | ||
return error.message; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { getRequestHeaders } from "@/lib/poweroffice/auth"; | ||
async function superget(env, url, workspaceId) { | ||
const poHeaders = await getRequestHeaders(env, workspaceId); | ||
const response = await fetch(url, { | ||
method: "GET", | ||
headers: poHeaders, | ||
}); | ||
if (!response.ok) { | ||
throw new Error(`Bad response while fetching ${url}: ${response.status} ${response.statusText}`); | ||
} | ||
const res = await response.json(); | ||
return res; | ||
} | ||
async function superpost(env, url, workspaceId, data) { | ||
const poHeaders = await getRequestHeaders(env, workspaceId); | ||
let response; | ||
try { | ||
response = await fetch(url, { | ||
method: "POST", | ||
headers: poHeaders, | ||
body: JSON.stringify(data), | ||
}); | ||
} | ||
catch (error) { | ||
console.error("Superpost error:", error); | ||
return; | ||
} | ||
if (!response.ok) { | ||
console.error(`Bad response while posting to ${url}: ${response.status} ${response.statusText} ${await response.text()}`); | ||
throw new Error(`Bad response while posting to ${url}: ${response.status} ${response.statusText}`); | ||
} | ||
const res = await response.json(); | ||
return res; | ||
} | ||
export { superget, superpost, }; |
Oops, something went wrong.