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: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,6 @@ dist
.tern-port

# Local Development
localDevelopment
docker-compose.yaml
.DS_Store
.env.local.compose
localDevelopment/postgres/prefect-api-keys-table.local.csv
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:current-alpine
FROM node:20-alpine

# Create app directory
WORKDIR /usr/src/app
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,33 @@ docker build -t prefect-auth-proxy .
docker run -d -p 3000:3000 --env-file ./.env --name auth-proxy prefect-auth-proxy
```

#### Local Development with Docker Compose

For local Docker Compose usage, generate the local-only password file and CSV scaffold first:

```bash
./scripts/start-local.sh --prepare-only
```

This creates:

- `.env.local.compose` with a generated `LOCAL_POSTGRES_PASSWORD`
- `localDevelopment/postgres/prefect-api-keys-table.local.csv`

Then add one or more local Prefect API key rows to `localDevelopment/postgres/prefect-api-keys-table.local.csv` and start the stack:

```bash
./scripts/start-local.sh
```

If Postgres was already initialized, run `docker compose down -v` before restarting so the CSV seed data is reloaded.

Example CSV row:

```csv
"localDev","{""GET"": [""api/*""]}","<generated api key>",2026-01-01 00:00:00.000,2027-01-01 00:00:00.000,2026-01-01 00:00:00.000,2026-01-01 00:00:00.000,"localDev","localDev"
```

### Running the service

This is a typical node.js application. So you can simply clone this repo and run.
Expand Down
7 changes: 4 additions & 3 deletions config/permitted-routes.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"GET" : ["*"],
"POST": ["*/filter", "*/count"]
}
"GET": ["/", "/dashboard", "/dashboard/*", "/assets/*", "/docs", "/openapi.json", "/ui-settings", "*.svg", "*.ico", "*.png"],
"POST": [],
"DELETE": []
}
4 changes: 2 additions & 2 deletions data/postgres/db_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ CREATE TABLE prefect_api_keys (
PRIMARY KEY (user_id)
);

insert into prefect_api_keys (user_id, scopes, api_key, key_issu_dt, key_expr_dt, creatd_dt, last_updatd_dt, creatd_by, last_updatd_by)
values('USERID', 'mutation/*, query/*', 'TestKey001', '2021-12-27 00:00:00', '2022-12-31 00:00:00', '2021-12-30 00:00:00', '2021-12-30 00:00:00', 'ADMINUSERID', 'ADMINUSERID')
insert into prefect_api_keys (user_id, scopes, api_key, key_issu_dt, key_expr_dt, creatd_dt, last_updatd_dt, creatd_by, last_updatd_by)
values('USERID', 'mutation/*, query/*, *', 'TestKey001', '2021-12-27 00:00:00', '2022-12-31 00:00:00', '2021-12-30 00:00:00', '2021-12-30 00:00:00', 'ADMINUSERID', 'ADMINUSERID')
46 changes: 46 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
version: "3.4"

services:
nginx:
image: nginx
volumes:
- ./localDevelopment/nginx/conf.d/:/etc/nginx/conf.d/
ports:
- "8080:80"
links:
- auth-proxy
depends_on:
- auth-proxy

auth-proxy:
image: localhost/auth-proxy
build:
dockerfile: ./Dockerfile
ports:
- "3000:3000"
volumes:
- ./index.js:/usr/src/app/index.js
- ./node_modules:/usr/src/app/node_modules
- ./config/permitted-routes.json:/usr/src/app/config/permitted-routes.json
environment:
- LOG_LEVEL=info
- API_SERVICE_URL=https://localhost:8080
# - ALLOW_PUBLIC_ACCESS=false
- DB_HOST=postgres
- DB_USER=postgres
- DB_PASSWORD=${LOCAL_POSTGRES_PASSWORD:?Run scripts/start-local.sh first}
- DB_DATABASE=postgres
links:
- postgres
depends_on:
- postgres

postgres:
image: postgres
environment:
POSTGRES_PASSWORD: ${LOCAL_POSTGRES_PASSWORD:?Run scripts/start-local.sh first}
volumes:
- ./localDevelopment/postgres:/docker-entrypoint-initdb.d
ports:
- "5432:5432"
56 changes: 41 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,20 @@ const checkRoutes = (url, routes) => {
return false;
}

regexRoutes = routes.map(route => route.replace(/\*/g, "[^ ]*"));

for (let i = 0; i < regexRoutes.length; i++) {
const match = url.match(regexRoutes[i]);

if (match) {
// Normalize URL - remove leading slash for comparison
const normalizedUrl = url.startsWith('/') ? url.substring(1) : url;

for (let i = 0; i < routes.length; i++) {
let pattern = routes[i];
// Normalize pattern - remove leading slash
pattern = pattern.startsWith('/') ? pattern.substring(1) : pattern;
// Convert * to .* for regex, escape special chars
// Handle /* as optional trailing slash + anything
pattern = pattern.replace(/\/\*/g, '(/.*)?');
pattern = pattern.replace(/\*/g, '.*');
const regex = new RegExp('^' + pattern + '$');

if (regex.test(normalizedUrl)) {
return true;
}
}
Expand All @@ -211,9 +219,19 @@ const checkRoutes = (url, routes) => {
};

const allowPassthrough = (url, method, acl) => {
if (config.ALLOW_PUBLIC_ACCESS) return true;
if (config.PERMITTED_ROUTES && config.PERMITTED_ROUTES[method]?.length && checkRoutes(url, config.PERMITTED_ROUTES[method])) return true;
if (checkRoutes(url, acl?.ops)) return true;
// check config for public access
if (config.ALLOW_PUBLIC_ACCESS) {
return true;
}
// check permitted routes
if (config.PERMITTED_ROUTES && config.PERMITTED_ROUTES[method]?.length && checkRoutes(url, config.PERMITTED_ROUTES[method])) {
return true;
}

// check acl
if (checkRoutes(url, acl?.ops)) {
return true;
}
return false;
};
// #endregion
Expand Down Expand Up @@ -247,25 +265,28 @@ app.use(async (req, res, next) => {
try {
const parsed = JSON.parse(rawScopes);
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
const normalized = {};
acl.scopesByMethod = {};
Object.entries(parsed).forEach(([methodKey, patterns]) => {
if (Array.isArray(patterns)) {
const expanded = patterns.flatMap(p => p.split(',')).map(p => p.trim()).filter(Boolean);
normalized[methodKey] = expanded;
acl.scopesByMethod[methodKey] = expanded;
}
});
// Only keep patterns relevant to this request method (fallback to '*')
acl.ops = normalized[req.method] || normalized['*'] || []
} else {
acl.ops = [];
acl.scopesByMethod = null;
}
} catch (e) {
console.warn('Invalid JSON scopes, falling back to list parsing');
acl.ops = rawScopes ? rawScopes.split(',').map(s => s.trim()).filter(Boolean) : [];
acl.scopesByMethod = null;
}
} else {
acl.scopesByMethod = null;
}

if (!acl.scopesByMethod) {
acl.ops = rawScopes ? rawScopes.split(',').map(s => s.trim()).filter(Boolean) : [];
}

console.log("ACL: ", acl);
tokenCache.set(apiKey, acl);
} else {
Expand All @@ -275,6 +296,11 @@ app.use(async (req, res, next) => {
};
}
}

if (acl.scopesByMethod) {
acl.ops = acl.scopesByMethod[req.method] || acl.scopesByMethod['*'] || [];
}

console.log("ACL: ", acl);
req.acl = acl;
}
Expand Down
17 changes: 17 additions & 0 deletions localDevelopment/nginx/conf.d/auth-proxy.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
server {
location /api/ {
# proxy_bind 127.0.0.1;
proxy_pass http://auth-proxy:3000;
# proxy_pass https://prefect2-server.dev.4innovation.io/api/;
}
location / {
# proxy_bind 127.0.0.1;
proxy_pass https://prefect2-server.dev.4innovation.internal.cms.gov/;
#proxy_pass https://prefectui.dev.4innovation.io/;
}
}





16 changes: 16 additions & 0 deletions localDevelopment/postgres/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
CREATE TABLE prefect_api_keys (
user_id varchar(128),
scopes varchar(2000),
api_key varchar(128),
key_issu_dt timestamp,
key_expr_dt timestamp,
created_dt timestamp,
last_updatd_dt timestamp,
creatd_by TEXT,
last_updatd_by TEXT
);

COPY prefect_api_keys
FROM '/docker-entrypoint-initdb.d/prefect-api-keys-table.local.csv'
DELIMITER ','
CSV HEADER;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"user_id","scopes","api_key","key_issu_dt","key_expr_dt","creatd_dt","last_updatd_dt","creatd_by","last_updatd_by"
Loading