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
23 changes: 23 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Docker build

on:
push:
branches:
- master

pull_request:
branches:
- master

env:
IMAGE_NAME: gatekeeper-mqtt

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7

- name: Run test
run: docker build . --file Dockerfile
21 changes: 21 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Lint

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: lint
run: |
npm ci
npm run lint
npm run format:check
10 changes: 2 additions & 8 deletions access.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export async function checkAccess(db, userId, doorId) {
userId: { $in: [userId, "*"] },
doorId: { $in: [doorId, "*"] },
},
{ sort: { priority: -1 } }
{ sort: { priority: -1 } },
);
if (userTicket !== null) return userTicket.granted;

Expand All @@ -17,13 +17,7 @@ export async function checkAccess(db, userId, doorId) {
doorId: { $in: ["*", doorId] },
groupId: { $in: dbUser.groups ? dbUser.groups.concat("*") : ["*"] },
},
{ sort: { priority: -1 } }
{ sort: { priority: -1 } },
);
return groupTicket?.granted;
}

export async function recordAudit(db, entry) {
db.collection("auditLogs").insertOne({ ...entry, timestamp: new Date() }).catch((err) => {
console.error("Failed to write audit entry", err);
});
}
17 changes: 17 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import js from "@eslint/js";
import globals from "globals";
import prettier from "eslint-config-prettier";

export default [
js.configs.recommended,
{
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: {
...globals.node,
},
},
},
prettier,
];
4 changes: 2 additions & 2 deletions ldap.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function searchOne(base, filter, attributes) {
res.once("searchEntry", onSearchEntry);
res.once("end", onEnd);
}
}
},
);
});
}
Expand All @@ -54,7 +54,7 @@ resolve("_ldap._tcp.csh.rit.edu").then((records) => {
throw err;
}
console.log("LDAP is bound!");
}
},
);
});
});
8 changes: 4 additions & 4 deletions metrics.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import StatsD from 'hot-shots';
import StatsD from "hot-shots";

export const statsd = new StatsD({
prefix: 'gatekeeper.',
globalTags: { service: 'gatekeeper-mqtt' },
errorHandler: (err) => console.error('StatsD error:', err),
prefix: "gatekeeper.",
globalTags: { service: "gatekeeper-mqtt" },
errorHandler: (err) => console.error("StatsD error:", err),
});
7 changes: 5 additions & 2 deletions middleware/hybridAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ export function hybridAuth(realm) {

if (authHeader.startsWith("Bearer ")) {
try {
const { userId, groups, username, name } = await validateToken(authHeader.slice(7), USER_SCOPE);
const { userId, groups, username, name } = await validateToken(
authHeader.slice(7),
USER_SCOPE,
);
req.ctx.userId = userId;
req.ctx.groups = groups;
req.ctx.username = username;
Expand All @@ -30,4 +33,4 @@ export function hybridAuth(realm) {
next();
}
};
}
}
32 changes: 16 additions & 16 deletions middleware/oidc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const USER_SCOPE = "gatekeeper_user";
const USER_ID_CLAIM = "uuid";

const issuerUrl = new URL(
process.env.GK_OIDC_ISSUER || "https://sso.csh.rit.edu/auth/realms/csh"
process.env.GK_OIDC_ISSUER || "https://sso.csh.rit.edu/auth/realms/csh",
);
export const clientId = process.env.GK_OIDC_CLIENT_ID;

Expand Down Expand Up @@ -35,7 +35,7 @@ export async function validateToken(token, requiredScope = null) {
let payload;
try {
({ payload } = await jwtVerify(token, JWKS, verifyOptions));
} catch (err) {
} catch {
throw new AuthError("Invalid or expired token", 401);
}

Expand All @@ -45,7 +45,7 @@ export async function validateToken(token, requiredScope = null) {
if (!scopes.includes(requiredScope)) {
throw new AuthError(
`Token missing required scope: ${requiredScope}`,
403
403,
);
}
}
Expand All @@ -62,19 +62,19 @@ export async function validateToken(token, requiredScope = null) {
}

export function oidcAuth(scope) {
return async function oidcAuth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ message: "Bearer token required" });
}
try {
const { userId } = await validateToken(authHeader.slice(7), scope);
req.ctx.userId = userId;
next();
} catch (err) {
return res.status(err.status || 401).json({ message: err.message });
}
}
return async function oidcAuth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ message: "Bearer token required" });
}
try {
const { userId } = await validateToken(authHeader.slice(7), scope);
req.ctx.userId = userId;
next();
} catch (err) {
return res.status(err.status || 401).json({ message: err.message });
}
};
}

export function requireGroup(group) {
Expand Down
14 changes: 12 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"main": "index.js",
"scripts": {
"start": "node server.js",
"kube": "GK_MQTT_SERVER=mqtts://$MOSQUITTO_SERVICE_HOST:$MOSQUITTO_SERVICE_PORT node server.js"
"kube": "GK_MQTT_SERVER=mqtts://$MOSQUITTO_SERVICE_HOST:$MOSQUITTO_SERVICE_PORT node server.js",
"lint": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"author": "Mary Strodl",
"license": "MIT",
Expand All @@ -21,5 +24,12 @@
"mqtt": "^4.2.6",
"openid-client": "^6.0.0"
},
"packageManager": "pnpm@11.3.0+sha512.2c403d6594527287672b1f7056343a1f7c3634036a67ffabfcc2b3d7595d843768f8787148d1b57cf7956c90606bbd192857c363af19e96d2d0ec9ec5741d215"
"packageManager": "pnpm@11.3.0+sha512.2c403d6594527287672b1f7056343a1f7c3634036a67ffabfcc2b3d7595d843768f8787148d1b57cf7956c90606bbd192857c363af19e96d2d0ec9ec5741d215",
"devDependencies": {
"@eslint/js": "^10.0.1",
"eslint": "^10.8.0",
"eslint-config-prettier": "^10.1.8",
"globals": "^17.9.0",
"prettier": "^3.9.6"
}
}
Loading