feat: add ragflow web project & add pnpm workspace file
This commit is contained in:
51
packages/auth-gateway/README.md
Normal file
51
packages/auth-gateway/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# @teres/auth-gateway
|
||||
|
||||
Minimal Node session service to share auth token via Cookie or API.
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
pnpm -F @teres/auth-gateway dev
|
||||
```
|
||||
|
||||
Default port: `7000`. Configure via env:
|
||||
|
||||
- `PORT=7000`
|
||||
- `ALLOWED_ORIGINS=http://localhost:5173,http://localhost:6006`
|
||||
- `COOKIE_NAME=sid`
|
||||
- `COOKIE_DOMAIN=` (optional)
|
||||
- `COOKIE_SECURE=false` (set `true` in HTTPS)
|
||||
- `COOKIE_SAMESITE=lax` (`lax|strict|none`)
|
||||
- `EXPOSE_TOKEN=true` (set `false` to hide token in GET response)
|
||||
|
||||
## Endpoints
|
||||
|
||||
- `GET /health` → `{ ok: true }`
|
||||
- `POST /auth/session` → set token; accepts JSON `{ token }` or `Authorization: Bearer <token>`
|
||||
- `GET /auth/session` → read session; returns `{ exists, updatedAt, token? }`
|
||||
- `DELETE /auth/session` → clear session and cookie
|
||||
|
||||
## Frontend usage
|
||||
|
||||
After login in host app:
|
||||
|
||||
```ts
|
||||
await fetch("http://localhost:7000/auth/session", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ token }),
|
||||
credentials: "include",
|
||||
});
|
||||
```
|
||||
|
||||
In iframe app (ragflow) to read the token (if `EXPOSE_TOKEN=true`):
|
||||
|
||||
```ts
|
||||
const res = await fetch("http://localhost:7000/auth/session", {
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await res.json();
|
||||
const token = data.token; // may be undefined if EXPOSE_TOKEN=false
|
||||
```
|
||||
|
||||
Alternatively, keep `EXPOSE_TOKEN=false` and use a backend that reads the cookie server-side. Or pass the token via your `iframe-bridge`/Penpal channel.
|
||||
28
packages/auth-gateway/package.json
Normal file
28
packages/auth-gateway/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@teres/auth-gateway",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Minimal Node session service to share auth token via Cookie or API.",
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsc -b",
|
||||
"start": "node dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cookie-parser": "^1.4.7",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "~5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
107
packages/auth-gateway/src/index.ts
Normal file
107
packages/auth-gateway/src/index.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import cookieParser from "cookie-parser";
|
||||
import dotenv from "dotenv";
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const PORT = Number(process.env.PORT || 7000);
|
||||
const COOKIE_NAME = process.env.COOKIE_NAME || "sid";
|
||||
const COOKIE_DOMAIN = process.env.COOKIE_DOMAIN || undefined; // e.g. your.dev.local
|
||||
const COOKIE_SECURE = String(process.env.COOKIE_SECURE || "false") === "true"; // true for https
|
||||
const COOKIE_SAMESITE = (process.env.COOKIE_SAMESITE || "lax") as
|
||||
| "lax"
|
||||
| "strict"
|
||||
| "none";
|
||||
const ALLOWED_ORIGINS = (process.env.ALLOWED_ORIGINS || "http://localhost:5173,http://localhost:6006")
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
const EXPOSE_TOKEN = String(process.env.EXPOSE_TOKEN || "true") !== "false"; // if false, GET won't return raw token
|
||||
|
||||
// In-memory store: sid -> token
|
||||
const store = new Map<string, { token: string; updatedAt: number }>();
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin(origin, cb) {
|
||||
if (!origin) return cb(null, true); // allow same-origin or curl
|
||||
if (ALLOWED_ORIGINS.includes(origin)) return cb(null, true);
|
||||
return cb(new Error("Not allowed by CORS"));
|
||||
},
|
||||
credentials: true,
|
||||
})
|
||||
);
|
||||
app.use(cookieParser());
|
||||
app.use(express.json());
|
||||
|
||||
// Ensure a session cookie exists
|
||||
app.use((req, res, next) => {
|
||||
let sid = req.cookies[COOKIE_NAME];
|
||||
if (!sid) {
|
||||
sid = randomUUID();
|
||||
res.cookie(COOKIE_NAME, sid, {
|
||||
httpOnly: true,
|
||||
sameSite: COOKIE_SAMESITE,
|
||||
secure: COOKIE_SECURE,
|
||||
domain: COOKIE_DOMAIN,
|
||||
path: "/",
|
||||
maxAge: 1000 * 60 * 60 * 24, // 1 day
|
||||
});
|
||||
}
|
||||
(req as any).sid = sid;
|
||||
next();
|
||||
});
|
||||
|
||||
app.get("/health", (_req, res) => {
|
||||
res.json({ ok: true, service: "auth-gateway", port: PORT });
|
||||
});
|
||||
|
||||
// Set token: accept JSON body { token } or Authorization: Bearer <token>
|
||||
app.post("/auth/session", (req, res) => {
|
||||
const sid: string = (req as any).sid;
|
||||
const bearer = req.header("authorization") || req.header("Authorization");
|
||||
let token = req.body?.token as string | undefined;
|
||||
if (!token && bearer && bearer.toLowerCase().startsWith("bearer ")) {
|
||||
token = bearer.slice(7);
|
||||
}
|
||||
if (!token) {
|
||||
return res.status(400).json({ ok: false, error: "Missing token" });
|
||||
}
|
||||
store.set(sid, { token, updatedAt: Date.now() });
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// Get token (if EXPOSE_TOKEN=true). Always returns session status.
|
||||
app.get("/auth/session", (req, res) => {
|
||||
const sid: string = (req as any).sid;
|
||||
const item = store.get(sid);
|
||||
const data: any = { ok: true, exists: Boolean(item), updatedAt: item?.updatedAt ?? null };
|
||||
if (EXPOSE_TOKEN && item) data.token = item.token;
|
||||
res.json(data);
|
||||
});
|
||||
|
||||
// Logout / clear token
|
||||
app.delete("/auth/session", (req, res) => {
|
||||
const sid: string = (req as any).sid;
|
||||
store.delete(sid);
|
||||
// Optionally clear cookie
|
||||
res.clearCookie(COOKIE_NAME, {
|
||||
httpOnly: true,
|
||||
sameSite: COOKIE_SAMESITE,
|
||||
secure: COOKIE_SECURE,
|
||||
domain: COOKIE_DOMAIN,
|
||||
path: "/",
|
||||
});
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Auth gateway running on http://localhost:${PORT}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Allowed origins: ${ALLOWED_ORIGINS.join(", ")}`);
|
||||
});
|
||||
9
packages/auth-gateway/tsconfig.json
Normal file
9
packages/auth-gateway/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.node.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"tsBuildInfoFile": "./dist/.tsbuildinfo"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
33
packages/iframe-bridge/package.json
Normal file
33
packages/iframe-bridge/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@teres/iframe-bridge",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Bridge utilities for host↔iframe communication (Penpal optional).",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"penpal": "^6.2.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"penpal": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
25
packages/shared-auth/package.json
Normal file
25
packages/shared-auth/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@teres/shared-auth",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Shared auth helpers for iframe-embedded apps (token exchange, storage).",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user