Compare commits

...

13 Commits
1.0.0 ... 1.0.4

Author SHA1 Message Date
3848eb939a Tweaked LOA API RBAC to allow full command group access
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m48s
Continuous Deployment / Update Deployment (push) Successful in 2m32s
2025-12-22 21:36:10 -05:00
a52f5cd31a Merge pull request 'added tracking for course book/qual states at report time' (#128) from Training-Report-Improvements into main
All checks were successful
Continuous Integration / Update Development (push) Successful in 3m28s
Reviewed-on: #128
2025-12-21 18:33:11 -06:00
46d1a0c286 added tracking for course book/qual states at report time 2025-12-21 19:21:12 -05:00
6c2b88352d Merge pull request 'Increased session longevity and implemented refresh system' (#126) from Session-length-extension into main
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m40s
Reviewed-on: #126
2025-12-20 10:12:32 -06:00
71f9240088 Merge branch 'main' into Session-length-extension 2025-12-20 10:12:26 -06:00
e35b61d06b Increased session longevity and implemented refresh system
also added type support for express-session
2025-12-20 11:13:28 -05:00
dc3430aa2e Merge pull request 'Removed hard dependency on discord ID for auth system' (#125) from Login-discord-decouple into main
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m51s
Reviewed-on: #125
2025-12-19 22:20:52 -06:00
ff5371d867 Removed hard dependency on discord ID for auth system 2025-12-19 22:46:53 -05:00
f3e35f3f6a improved robustness of logout function
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m29s
2025-12-17 19:46:30 -05:00
d7b099ac75 fixed for reals this time
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m26s
Continuous Deployment / Update Deployment (push) Successful in 2m26s
2025-12-17 17:20:28 -05:00
a6b521a89c Fixed hardcoded database name
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m27s
Continuous Deployment / Update Deployment (push) Successful in 2m24s
2025-12-17 17:15:33 -05:00
ad4d28b5dd Made calendar cancel button red
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m23s
2025-12-17 13:11:24 -05:00
ac22e36202 Fixed everyone getting my roles
All checks were successful
Continuous Integration / Update Development (push) Successful in 2m23s
Continuous Deployment / Update Deployment (push) Successful in 2m25s
2025-12-17 12:57:09 -05:00
9 changed files with 67 additions and 42 deletions

20
api/package-lock.json generated
View File

@@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"@sentry/node": "^10.27.0",
"@types/express-session": "^1.18.2",
"chalk": "^5.6.2",
"connect-sqlite3": "^0.9.16",
"cors": "^2.8.5",
@@ -758,7 +759,6 @@
"version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/connect": "*",
@@ -778,7 +778,6 @@
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz",
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/body-parser": "*",
@@ -790,7 +789,6 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz",
"integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
@@ -799,6 +797,15 @@
"@types/send": "*"
}
},
"node_modules/@types/express-session": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz",
"integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==",
"license": "MIT",
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
@@ -809,14 +816,12 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/morgan": {
@@ -871,21 +876,18 @@
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/send": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz",
"integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
@@ -895,7 +897,6 @@
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz",
"integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/http-errors": "*",
@@ -907,7 +908,6 @@
"version": "0.17.5",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
"integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/mime": "^1",

View File

@@ -13,6 +13,7 @@
},
"dependencies": {
"@sentry/node": "^10.27.0",
"@types/express-session": "^1.18.2",
"chalk": "^5.6.2",
"connect-sqlite3": "^0.9.16",
"cors": "^2.8.5",

View File

@@ -12,7 +12,7 @@ const pool = mariadb.createPool({
connectionLimit: 5,
connectTimeout: 10000, // give it more breathing room
acquireTimeout: 15000,
database: 'ranger_unit_tracker',
database: process.env.DB_DATABASE,
ssl: false,
});

View File

@@ -55,21 +55,27 @@ if (process.env.DISABLE_GLITCHTIP === "true") {
//session setup
import path = require('path');
// import session = require('express-session');
import session = require('express-session');
import passport = require('passport');
const SQLiteStore = require('connect-sqlite3')(session);
app.use(session({
const cookieOptions: session.CookieOptions = {
httpOnly: true,
sameSite: 'lax',
domain: process.env.CLIENT_DOMAIN,
maxAge: 1000 * 60 * 60 * 24 * 30, //30 days
}
const sessionOptions: session.SessionOptions = {
secret: 'whatever',
resave: false,
saveUninitialized: false,
store: new SQLiteStore({ db: 'sessions.db', dir: './' }),
cookie: {
httpOnly: true,
sameSite: 'lax',
domain: process.env.CLIENT_DOMAIN
}
}));
rolling: true,
cookie: cookieOptions
}
app.use(session(sessionOptions));
app.use(passport.authenticate('session'));
// Mount route modules

View File

@@ -46,32 +46,35 @@ passport.use(new OpenIDConnectStrategy({
//lookup existing user
const existing = await con.query(`SELECT id FROM members WHERE authentik_issuer = ? AND authentik_sub = ? LIMIT 1;`, [issuer, sub]);
let memberId: number;
let memberId: number | null = null;
//if member exists
if (existing.length > 0) {
memberId = existing[0].id;
} else {
//otherwise: create account
//otherwise: create account mode
const jwt = parseJwt(jwtClaims);
const discordID = jwt.discord.id as number;
const discordID = jwt.discord?.id as number;
//check if account is available to claim
memberId = await mapDiscordtoID(discordID);
if (discordID)
memberId = await mapDiscordtoID(discordID);
if (memberId === null) {
// create new account
if (discordID && memberId) {
// claim account
console.log("Claiming account");
const result = await con.query(
`UPDATE members SET authentik_sub = ?, authentik_issuer = ? WHERE id = ?;`,
[sub, issuer, memberId]
)
} else {
console.log("New Account");
// new account
const username = sub.username;
const result = await con.query(
`INSERT INTO members (name, authentik_sub, authentik_issuer) VALUES (?, ?, ?)`,
[username, sub, issuer]
)
memberId = Number(result.insertId);
} else {
// claim existing account
const result = await con.query(
`UPDATE members SET authentik_sub = ?, authentik_issuer = ? WHERE id = ?;`,
[sub, issuer, memberId]
)
}
}
@@ -115,11 +118,24 @@ router.get('/callback', (req, res, next) => {
router.get('/logout', [requireLogin], function (req, res, next) {
req.logout(function (err) {
if (err) { return next(err); }
var params = {
client_id: process.env.AUTH_CLIENT_ID,
returnTo: process.env.CLIENT_URL
};
res.redirect(process.env.AUTH_END_SESSION_URI + '?' + querystring.stringify(params));
req.session.destroy((err) => {
if (err) { return next(err); }
res.clearCookie('connect.sid', {
path: '/',
domain: process.env.CLIENT_DOMAIN,
httpOnly: true,
sameSite: 'lax'
});
var params = {
client_id: process.env.AUTH_CLIENT_ID,
returnTo: process.env.CLIENT_URL
};
res.redirect(process.env.AUTH_END_SESSION_URI + '?' + querystring.stringify(params));
})
});
});

View File

@@ -26,7 +26,7 @@ router.post("/", async (req: Request, res: Response) => {
});
//admin posts LOA
router.post("/admin", [requireRole("17th Administrator")], async (req: Request, res: Response) => {
router.post("/admin", [requireRole(['17th Administrator', '17th HQ', '17th Command'])], async (req: Request, res: Response) => {
let LOARequest = req.body as LOARequest;
LOARequest.created_by = req.user.id;
LOARequest.filed_date = new Date();
@@ -67,7 +67,7 @@ router.get("/history", async (req: Request, res: Response) => {
}
})
router.get('/all', [requireRole("17th Administrator")], async (req: Request, res: Response) => {
router.get('/all', [requireRole(['17th Administrator', '17th HQ', '17th Command'])], async (req: Request, res: Response) => {
try {
const page = Number(req.query.page) || undefined;
const pageSize = Number(req.query.pageSize) || undefined;
@@ -107,7 +107,7 @@ router.post('/cancel/:id', async (req: Request, res: Response) => {
})
//TODO: enforce admin only
router.post('/adminCancel/:id', [requireRole("17th Administrator")], async (req: Request, res: Response) => {
router.post('/adminCancel/:id', [requireRole(['17th Administrator', '17th HQ', '17th Command'])], async (req: Request, res: Response) => {
let closer = req.user.id;
try {
await closeLOA(Number(req.params.id), closer);
@@ -119,7 +119,7 @@ router.post('/adminCancel/:id', [requireRole("17th Administrator")], async (req:
})
// TODO: Enforce admin only
router.post('/extend/:id', [requireRole("17th Administrator")], async (req: Request, res: Response) => {
router.post('/extend/:id', [requireRole(['17th Administrator', '17th HQ', '17th Command'])], async (req: Request, res: Response) => {
const to: Date = req.body.to;
if (!to) {

View File

@@ -83,8 +83,10 @@ export async function insertCourseEvent(event: CourseEventDetails): Promise<numb
try {
var con = await pool.getConnection();
let course: Course = await getCourseByID(event.course_id);
await con.beginTransaction();
const res = await con.query("INSERT INTO course_events (course_id, event_date, remarks, created_by) VALUES (?, ?, ?, ?);", [event.course_id, toDateTime(event.event_date), event.remarks, event.created_by]);
const res = await con.query("INSERT INTO course_events (course_id, event_date, remarks, created_by, hasBookwork, hasQual) VALUES (?, ?, ?, ?, ?, ?);", [event.course_id, toDateTime(event.event_date), event.remarks, event.created_by, course.hasBookwork, course.hasQual]);
var eventID: number = res.insertId;
for (const attendee of event.attendees) {

View File

@@ -21,7 +21,7 @@ export async function getUserRoles(userID: number): Promise<Role[]> {
const sql = `SELECT r.id, r.name
FROM members_roles mr
INNER JOIN roles r ON mr.role_id = r.id
WHERE mr.member_id = 190;`;
WHERE mr.member_id = ?;`;
return await pool.query(sql, [userID]);
}

View File

@@ -197,7 +197,7 @@ defineExpose({ forceReload })
<DropdownMenuItem v-if="activeEvent.cancelled" @click="setCancel(false)">
Un-Cancel
</DropdownMenuItem>
<DropdownMenuItem v-else @click="setCancel(true)">
<DropdownMenuItem v-else @click="setCancel(true)" class="text-destructive">
Cancel
</DropdownMenuItem>
</DropdownMenuContent>