First commit

This commit is contained in:
Sachin
2025-12-16 22:26:18 +05:30
commit 03ed187ebe
122 changed files with 68601 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
export const config = {
env: (process && process.env && process.env.NODE_ENV) || 'development',
port: Number(process?.env?.PORT) || 4000,
host: process?.env?.HOST || '0.0.0.0',
version: '0.0.0',
cors: { allowedOrigins: ['*'] },
permitio: { pdpUrl: process?.env?.PERMIT_PDP_URL || '', apiKey: process?.env?.PERMIT_API_KEY || '' }
};

View File

@@ -0,0 +1,5 @@
export type Context = any;
export function createContext({ request, reply }: { request?: any; reply?: any }): Context {
return { request, reply } as Context;
}

View File

@@ -0,0 +1,5 @@
export const resolvers = {
Query: {
_empty: () => 'ok'
}
};

View File

@@ -0,0 +1,3 @@
export const typeDefs = `
type Query { _empty: String }
`;

9
apps/api-bff/src/main.ts Normal file
View File

@@ -0,0 +1,9 @@
import express from "express";
import projectsRouter from "./routes/projects";
const app = express();
app.use(express.json());
app.use("/api", projectsRouter);
app.listen(3000, () => console.log("Server running on http://localhost:3000"));

View File

@@ -0,0 +1,6 @@
export const authMiddleware = async (request: any, reply: any) => {
// stub: authenticate request and attach `user` to request in real implementation
request.user = request.user || { sub: 'anonymous' };
};
export default authMiddleware;

View File

@@ -0,0 +1,5 @@
export const dpopMiddleware = async (request: any, reply: any) => {
// stub: validate DPoP headers in real implementation
};
export default dpopMiddleware;

View File

@@ -0,0 +1,6 @@
export const policyMiddleware = async (request: any, reply: any) => {
// stub: evaluate ABAC policies; attach decisions to request
request.policy = { allowed: true };
};
export default policyMiddleware;

View File

@@ -0,0 +1,57 @@
import { Request, Response, NextFunction } from "express";
import { checkAccess } from "../services/authz";
type CrudAction = "create" | "read" | "update" | "delete" | "admin" | "view";
function isNonEmptyString(v: unknown): v is string {
return typeof v === "string" && v.trim().length > 0;
}
export function requireAccess(
getConfig: (req: Request) => {
userId: string;
resourceName: string;
action: CrudAction;
}
) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const cfg = getConfig(req);
const userId = cfg?.userId;
const resourceName = cfg?.resourceName;
const action = cfg?.action;
// ✅ Validate early (prevents calling OPA with undefined/null)
if (!isNonEmptyString(userId) || !isNonEmptyString(resourceName) || !isNonEmptyString(action)) {
return res.status(400).json({
message: "Missing or invalid authorization inputs",
details: {
userId: userId ?? null,
resourceName: resourceName ?? null,
action: action ?? null,
},
});
}
console.log("AUTHZ INPUT:", { userId, resourceName, action });
// checkAccess MUST build:
// { input: { user: {id:userId}, resource:{name:resourceName}, action } }
const allowed = await checkAccess({ userId, resourceName, action });
console.log("AUTHZ RESULT:", allowed);
if (!allowed) {
return res.status(403).json({ message: "Forbidden: access denied" });
}
return next();
} catch (e: any) {
return res.status(502).json({
message: "Authorization service error",
error: e?.message ?? "Unknown error",
});
}
};
}

View File

@@ -0,0 +1,5 @@
export const rateLimitPlugin = async (server: any, opts: any) => {
// stub: register rate limiting hooks
};
export default rateLimitPlugin;

View File

@@ -0,0 +1,5 @@
export const securityPlugin = async (server: any, opts: any) => {
// stub: set security-related Fastify hooks
};
export default securityPlugin;

View File

@@ -0,0 +1,58 @@
import { Router } from "express";
import { requireAccess } from "../middleware/requireAccess";
const router = Router();
// CREATE (uses payload values)
router.post(
"/projects",
requireAccess((req) => ({
userId: req.body.userId,
resourceName: req.body.resourceName,
action: req.body.action,
})),
async (req, res) => {
res.json({ message: "Project created" });
}
);
// READ (if you still want body-based auth)
router.get(
"/projects/:name",
requireAccess((req) => ({
userId: req.body.userId,
resourceName: req.body.resourceName ?? req.params.name,
action: req.body.action ?? "read",
})),
async (req, res) => {
res.json({ name: req.params.name });
}
);
// UPDATE (body-based)
router.put(
"/projects/:name",
requireAccess((req) => ({
userId: req.body.userId,
resourceName: req.body.resourceName ?? req.params.name,
action: req.body.action ?? "update",
})),
async (req, res) => {
res.json({ message: "Project updated" });
}
);
// DELETE (body-based)
router.delete(
"/projects/:name",
requireAccess((req) => ({
userId: req.body.userId,
resourceName: req.body.resourceName ?? req.params.name,
action: req.body.action ?? "delete",
})),
async (req, res) => {
res.json({ message: "Project deleted" });
}
);
export default router;

View File

@@ -0,0 +1,15 @@
import { opaAllow } from "./opaClient";
export async function checkAccess(params: {
userId: string;
resourceName: string;
action: string;
}): Promise<boolean> {
return opaAllow({
input: {
user: { id: params.userId },
resource: { name: params.resourceName },
action: params.action,
},
});
}

View File

@@ -0,0 +1,16 @@
import axios from "axios";
import { AccessRequest, AccessResponse } from "../types/authz";
const OPA_URL = "http://64.227.108.180:8182/v1/data/authz/access/allow";
export async function opaAllow(payload: AccessRequest): Promise<boolean> {
console.log("OPA REQUEST:", JSON.stringify(payload, null, 2));
const res = await axios.post<AccessResponse>(OPA_URL, payload, {
headers: { "Content-Type": "application/json" },
timeout: 5000,
});
console.log("OPA RESPONSE:", JSON.stringify(res.data, null, 2));
return Boolean(res.data?.result);
}

38
apps/api-bff/src/shims.d.ts vendored Normal file
View File

@@ -0,0 +1,38 @@
/* Minimal external module shims for static analysis. Keep these permissive. */
declare module 'fastify' {
const Fastify: any;
export type FastifyInstance = any;
export default Fastify;
}
declare module '@apollo/server' {
export class ApolloServer<T = any> {
constructor(options?: any);
start?(): Promise<void>;
stop?(): Promise<void>;
}
}
declare module '@as-integrations/fastify' {
const fastifyApollo: any;
export default fastifyApollo;
export const fastifyApolloDrainPlugin: any;
}
declare module '@apollo/server-plugin-landing-page-local-default' {
export const ApolloServerPluginLandingPageLocalDefault: any;
}
declare module '@graphql-tools/schema' {
export function makeExecutableSchema(...args: any[]): any;
}
declare module '@permitio/permit-node' {
export class Permit { constructor(opts?: any); check(...args: any[]): Promise<any>; }
}
declare module '@fastify/cors';
declare module '@fastify/helmet';
declare var process: any;

View File

@@ -0,0 +1,12 @@
package permission
import data.role_permissions
default allow := false
allow if {
some role in input.subject.roles
some permission in role_permissions[role]
permission.action == input.action
permission.object == input.object
}

View File

@@ -0,0 +1,7 @@
{
"subject": {
"roles": [
"admin"
]
}
}

View File

@@ -0,0 +1,11 @@
export interface AccessRequest {
input: {
user: { id: string };
resource: { name: string };
action: string; // "admin" | "view" | "read" | "create" | etc.
};
}
export interface AccessResponse {
result: boolean;
}

View File

@@ -0,0 +1 @@
export const logger = console;