Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
DOTENV_PUBLIC_KEY="029b5432287e802a315a922dd9d54d39c60a9c2b90352e6e182c7ebd760852b510"

# .env
VITE_APP_URL="encrypted:BKcBVZirrvVu+AvBZ/wTLs/WSV5Um4aoyzFDVZDpxZiUhR+VDEq/sZwSPAFW7sATCFJ2mK4xg1lm0nedVkgUjD9gtxkKnuaDzX/lPJFRxjms1OVfsPR84zgDeOr3PPCSHKJH0IFiuftDty8raz4T0phym6NMWD6r6Cq5czVh/VsBaEo="
VITE_AUTH_URL="encrypted:BMTSeTP2UnGyYdCPzERU0pgI6QFx0gBAFPEhDnqyZ05gzuE36I3Ug+/xLJCwCP3nTzICjAN5uc6BcWV+ZEv8kx869vmtxx6g50IY1uT1vwhlKYyQ/KCVy0/AOhCXIvLpSBHoD99ZHNJBhIHfhYcWO4HzJysXvTsPJixSMQ=="
VITE_BACKEND_URL="encrypted:BMMG/DnV11/An+JaGXeMl6YsSzwU3DVhf52hVsmx8vvIIMDyW0GcvqoPiqTGCV39YFK+6ljR/MgUfUTiqIl1g+cvCHM+UDRVvdN2F0ZwajKPY2FwJN5KBJFPFwra3JaGgFw5RU/aISnkBwQNPl6lQzPD/sWCXCNeli1YcIDwKkJIqZY="

BASIC_AUTH_USERNAME="encrypted:BHmmR1xRRdkt+4qh4Y54Q05JwK0R/n9KavesX5kIMscNlekzppOVKG31s9VTrSmjer0601B+zHV3ySQRKlQ79MjeRddi6v8fzsmmzrNy7/lne42RtMpfBGRTiNSt6x3D5mT7S0EI"
BASIC_AUTH_PASSWORD="encrypted:BPsyMs9LTwjtb9DvNjANPL9e0Mpv+/GRdcXpQi1H3DshaUEdtfhidRbAlYLCiybCcLY1FSueqGDw38FI4ajX369gzv827+Qq8lktwsydfdQxzckwGR27+8ro4U2NPnfCiGn7kmx56ut1Cu5q4Q=="

Expand Down
3 changes: 0 additions & 3 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
DOTENV_PUBLIC_KEY_PRODUCTION="032a0e48662d601c31f2f994ec75e746d2d336d58f361245ecf97cbaefb731e401"

# .env.production
VITE_APP_URL="encrypted:BMy514mbT+b57c3iY4+3K3ZHCz/xwvnItb1oB9hJZqSeYThRuLjZ1Qb7k2qmMRbAVG6lbhUafXWVIMdPQe3gYQ729MXLiek709MAERZclIMaLdjHBVsebF/W98v6gBYONGNEZ3X+pv5Q8MgDcarSvKi7ykv9Ftk="
VITE_AUTH_URL="encrypted:BGwAFe+0RQYlBwS/bklQmRggwJ8pzDDfBjDQxCOWg5HMXh2jGmJ/0qXyvilB5m+P0ppN6gaJTK91+fc6dtq40M62uCPv2qJO815p3vW3Ns0fEuHUHR02j5duejsZO2KpMoo1ME7a9pZ/fdPcPIs4EBgrA+tsWdJrq7f68A=="

DATABASE_URL="encrypted:BHxKxSCBnMpR0akH0ppveHcgLRtvX/En5XykATRdWaQQZhuTdQss99EROEK8gQA0bCrqJMEyGRYrB71oYvrhHuFEe+JpNhOeSkk3Yf0S5wso31oa13eI1F8jAR3VuAAdbHJgdVAxT/Wcn6rGqwI2THADM/Wkw1uamAWNn0RAEspv6OdubVKVLofPuHSlUChh2eo="
GOOGL_GENERATIVE_AI_API_KEY="encrypted:BJJytvF8IsqMvKHqrOqT/SSbyzKmk+5I+Zc2URLuHMZWC6vRM+zBvaQsn+n1yH/SmDzPL1oSdtb9vHhAMxCRos5dzFrtnvY63OSkj5RXlp2VtOOr6BLTEI5P+fB+JWJdsajkK0x9cNilM3TsO+9t7dg2twlmShaiTNs50ieatpiZm9Nq0QhNow=="

Expand Down
1 change: 1 addition & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"dependencies": {
"@ai-sdk/google": "^1.2.11",
"@hono/arktype-validator": "^2.0.1",
"@rectangular-labs/auth": "workspace:*",
"@rectangular-labs/db": "workspace:*",
"@rectangular-labs/env": "workspace:*",
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/lib/ai/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ export const niceClassificationModel = google("gemini-2.5-pro-preview-03-25");

// Model for Goods & Services recommendations (structured output)
export const goodsServicesModel = google("gemini-2.5-pro-preview-03-25");

export const completionModel = google("gemini-2.0-flash-001");
43 changes: 43 additions & 0 deletions apps/backend/src/lib/auth/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { createClient } from "@rectangular-labs/auth/client";
import { type UserSubject, subjects } from "@rectangular-labs/auth/subject";
import { type Result, safe } from "@rectangular-labs/result";
import { env } from "../env";
import { setSession } from "./session";

export const authClient = () => {
return createClient({
clientID: "rectangular-labs-backend",
issuer: env().VITE_AUTH_URL,
});
};

export async function verifySafe({
access,
refresh,
autoRefresh = true,
}: {
access: string;
refresh: string;
autoRefresh?: boolean;
}): Promise<Result<UserSubject, Error>> {
const verifiedResult = await safe(() =>
authClient().verify(subjects, access, {
refresh,
}),
);
if (!verifiedResult.ok) {
console.error("Failed to verify", verifiedResult.error);
return verifiedResult;
}
const verified = verifiedResult.value;
if (verified.err) {
return {
ok: false,
error: verified.err,
};
}
if (autoRefresh && verified.tokens) {
setSession(verified.tokens.access, verified.tokens.refresh);
}
return { ok: true, value: verified.subject.properties };
}
23 changes: 23 additions & 0 deletions apps/backend/src/lib/auth/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { UserSubject } from "@rectangular-labs/auth/subject";
import { getCookie } from "hono/cookie";
import { createMiddleware } from "hono/factory";
import { verifySafe } from "./client";

export const authMiddleware = createMiddleware<{
Variables: {
userSubject: UserSubject;
};
}>(async (c, next) => {
const access = getCookie(c, "access_token");
const refresh = getCookie(c, "refresh_token");
if (!access || !refresh) {
return c.json({ error: "Unauthorized" }, 401);
}
const verified = await verifySafe({ access, refresh });
if (!verified.ok) {
return c.json({ error: "Unauthorized" }, 401);
}
c.set("userSubject", verified.value);

return await next();
});
53 changes: 53 additions & 0 deletions apps/backend/src/lib/auth/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { getContext } from "hono/context-storage";
import { deleteCookie, setCookie } from "hono/cookie";
import { env } from "../env";

export function setSession(accessToken?: string, refreshToken?: string) {
const context = getContext();
if (accessToken) {
setCookie(context, "access_token", accessToken, {
httpOnly: true,
secure: true,
sameSite: "Lax",
path: "/",
domain: env().VITE_APP_URL.includes("localhost")
? "localhost"
: `.${env().VITE_APP_URL.replace("https://", "")}`,
maxAge: 34560000,
});
}
if (refreshToken) {
setCookie(context, "refresh_token", refreshToken, {
httpOnly: true,
secure: true,
sameSite: "Lax",
path: "/",
domain: env().VITE_APP_URL.includes("localhost")
? "localhost"
: `.${env().VITE_APP_URL.replace("https://", "")}`,
maxAge: 34560000,
});
}
}

export function deleteSession() {
const context = getContext();
const accessToken = deleteCookie(context, "access_token", {
secure: true,
httpOnly: true,
path: "/",
domain: env().VITE_APP_URL.includes("localhost")
? "localhost"
: `.${env().VITE_APP_URL.replace("https://", "")}`,
});
const refreshToken = deleteCookie(context, "refresh_token", {
secure: true,
httpOnly: true,
path: "/",
domain: env().VITE_APP_URL.includes("localhost")
? "localhost"
: `.${env().VITE_APP_URL.replace("https://", "")}`,
});
console.log("deleted new", { accessToken, refreshToken });
return !!accessToken && !!refreshToken;
}
52 changes: 52 additions & 0 deletions apps/backend/src/routes/api/auth/[...route].ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Hono } from "hono";
import { authClient, verifySafe } from "../../../lib/auth/client";
import { authMiddleware } from "../../../lib/auth/middleware";
import { deleteSession, setSession } from "../../../lib/auth/session";
import { env } from "../../../lib/env";
import type { HonoEnv } from "../../../lib/hono";

export const authRouter = new Hono<HonoEnv>()
.basePath("/api/auth")
.get("/me", authMiddleware, (c) => {
const userSubject = c.get("userSubject");
console.log("me", { userSubject });

return c.json({ userSubject });
})
.get("/authorize", async (c) => {
const callbackUrl = `${env().VITE_APP_URL}/api/auth/callback`;
const { url: redirectUrl } = await authClient().authorize(
callbackUrl,
"code",
);
return c.redirect(redirectUrl, 302);
})
.get("/callback", async (c) => {
const pathname = new URL(c.req.url).pathname;
const code = c.req.query("code");
if (!code) throw new Error("Missing code");
const exchanged = await authClient().exchange(
code,
`${env().VITE_APP_URL}${pathname}`,
);
if (exchanged.err)
return new Response(exchanged.err.toString(), {
status: 400,
});
setSession(exchanged.tokens.access, exchanged.tokens.refresh);

const verified = await verifySafe({
access: exchanged.tokens.access,
refresh: exchanged.tokens.refresh,
});
if (!verified.ok) {
return c.json({ error: "Unauthorized" }, 401);
}

return c.redirect(`${env().VITE_APP_URL}`, 302);
})
.post("/logout", authMiddleware, (c) => {
const deleted = deleteSession();
console.log("deleted", deleted);
return c.json({ message: deleted ? "OK" : "No Session" }, 200);
});
8 changes: 0 additions & 8 deletions apps/backend/src/routes/api/auth/_lib/client.ts

This file was deleted.

26 changes: 0 additions & 26 deletions apps/backend/src/routes/api/auth/_lib/session.ts

This file was deleted.

57 changes: 0 additions & 57 deletions apps/backend/src/routes/api/auth/route.ts

This file was deleted.

Loading