feat(intial auth): intial auth setup for the scanner
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled
This commit is contained in:
46
backend/mobile/donwloadApps.route.ts
Normal file
46
backend/mobile/donwloadApps.route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import fs from "node:fs";
|
||||
import { Router } from "express";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const router = Router();
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const downloadDir = path.resolve(__dirname, "../../downloads/mobile");
|
||||
|
||||
const currentApk = {
|
||||
fileName: "lst-mobile.apk",
|
||||
};
|
||||
|
||||
router.get("/latest", (_, res) => {
|
||||
const apkPath = path.join(downloadDir, currentApk.fileName);
|
||||
|
||||
if (!fs.existsSync(apkPath)) {
|
||||
return res.status(404).json({ success: false, message: "APK not found" });
|
||||
}
|
||||
|
||||
res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
||||
res.setHeader(
|
||||
"Content-Disposition",
|
||||
`attachment; filename="${currentApk.fileName}"`,
|
||||
);
|
||||
|
||||
return res.sendFile(apkPath);
|
||||
});
|
||||
|
||||
router.get("/ehs", (_, res) => {
|
||||
const apkPath = path.join(downloadDir, "EHS.apk");
|
||||
|
||||
if (!fs.existsSync(apkPath)) {
|
||||
return res.status(404).json({ success: false, message: "APK not found" });
|
||||
}
|
||||
|
||||
res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
||||
res.setHeader("Content-Disposition", `attachment; filename="EHS.apk}"`);
|
||||
|
||||
return res.sendFile(apkPath);
|
||||
});
|
||||
|
||||
export default router;
|
||||
17
backend/mobile/mobile.routes.ts
Normal file
17
backend/mobile/mobile.routes.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Express } from "express";
|
||||
import downloads from "./donwloadApps.route.js";
|
||||
import authPin from "./mobileAuth.route.js";
|
||||
import newPin from "./mobilePin.route.js";
|
||||
import logs from "./scanLogs.route.js";
|
||||
import version from "./version.route.js";
|
||||
|
||||
export const setupMobileRoutes = (baseUrl: string, app: Express) => {
|
||||
//stats will be like this as we dont need to change this
|
||||
app.use(`${baseUrl}/api/mobile/version`, version);
|
||||
app.use(`${baseUrl}/api/mobile/apk`, downloads);
|
||||
app.use(`${baseUrl}/api/mobile/logs`, logs);
|
||||
app.use(`${baseUrl}/api/mobile/auth`, authPin);
|
||||
app.use(`${baseUrl}/api/mobile/pin`, newPin);
|
||||
|
||||
// all other system should be under /api/system/*
|
||||
};
|
||||
331
backend/mobile/mobileAuth.route.ts
Normal file
331
backend/mobile/mobileAuth.route.ts
Normal file
@@ -0,0 +1,331 @@
|
||||
import bcrypt from "bcryptjs";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
import z from "zod";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import {
|
||||
type NewScanUser,
|
||||
type ScanUser,
|
||||
scanUser,
|
||||
} from "../db/schema/scanUsers.js";
|
||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
import { apiReturn, returnFunc } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
export async function hashPin(pin: string) {
|
||||
// if (!/^\d{6}$/.test(pin)) {
|
||||
// throw new Error("PIN must be exactly 6 digits");
|
||||
// }
|
||||
|
||||
return bcrypt.hashSync(pin, 12);
|
||||
}
|
||||
|
||||
const registerSchema = z.object({
|
||||
name: z.string().min(2).max(100),
|
||||
pinNumber: z.string(),
|
||||
scannerId: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(500)
|
||||
.optional()
|
||||
.describe("if you leave blank it will be the same as your username"),
|
||||
role: z
|
||||
.enum(["user", "lead", "manager", "admin"])
|
||||
.optional()
|
||||
.describe("What roles are available to use."),
|
||||
pinHash: z.string().optional(),
|
||||
});
|
||||
|
||||
r.post("/pin", async (req, res) => {
|
||||
const { pin } = req.body;
|
||||
|
||||
if (!pin || pin.length !== 6) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `Pin number must be a min of 6 digits`,
|
||||
data: [],
|
||||
status: 401,
|
||||
});
|
||||
}
|
||||
|
||||
// const user = await db
|
||||
// .select()
|
||||
// .from(scanUser)
|
||||
// .where(eq(scanUser.pinNumber, parseInt(pin, 10)));
|
||||
|
||||
const user = await db.query.scanUser.findFirst({
|
||||
where: (u, { eq }) => eq(u.pinNumber, pin),
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `Invalid login please try again.`,
|
||||
data: [],
|
||||
status: 401,
|
||||
});
|
||||
}
|
||||
|
||||
const validPin = bcrypt.compareSync(pin, user.pinHash);
|
||||
|
||||
if (!validPin) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `Invalid pin please try again.`,
|
||||
data: [],
|
||||
status: 401,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `Welcome back ${user.name}`,
|
||||
data: user as ScanUser | any,
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
r.post("/user", async (req, res) => {
|
||||
try {
|
||||
// validate the body is correct before accepting it
|
||||
let validated = registerSchema.parse(req.body);
|
||||
|
||||
validated = {
|
||||
...validated,
|
||||
pinHash: await hashPin(validated.pinNumber.toString()),
|
||||
};
|
||||
|
||||
const values: NewScanUser = {
|
||||
name: validated.name,
|
||||
pinNumber: validated.pinNumber,
|
||||
pinHash: validated.pinHash ?? "",
|
||||
scannerId: validated.scannerId ?? "",
|
||||
};
|
||||
|
||||
const newUser = await db.insert(scanUser).values(values).returning();
|
||||
|
||||
apiReturn(res, {
|
||||
success: true,
|
||||
level: "info", //connect.success ? "info" : "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `${validated.name} was just created`,
|
||||
data: newUser as any,
|
||||
status: 200, //connect.success ? 200 : 400,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof z.ZodError) {
|
||||
const flattened = z.flattenError(err);
|
||||
// return res.status(400).json({
|
||||
// error: "Validation failed",
|
||||
// details: flattened,
|
||||
// });
|
||||
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error", //connect.success ? "info" : "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: "Validation failed",
|
||||
data: [flattened.fieldErrors],
|
||||
status: 400, //connect.success ? 200 : 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error", //connect.success ? "info" : "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message:
|
||||
"This User already exist with this pin or scanner id please try again",
|
||||
data: [err],
|
||||
status: 400, //connect.success ? 200 : 400,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
r.get("/user", requireAuth, async (_, res) => {
|
||||
const { data, error } = await tryCatch(db.select().from(scanUser));
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `There was an error getting the user`,
|
||||
data: error as any,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `There are no users you should add one . `,
|
||||
data: [],
|
||||
status: 200,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `All users. `,
|
||||
data,
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
r.patch("/user/:id", requireAuth, async (req, res) => {
|
||||
const updates: Record<string, unknown | null> = {};
|
||||
const { id } = req.params;
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db.query.scanUser.findFirst({
|
||||
where: (u, { eq }) => eq(u.id, `${id}`),
|
||||
}),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `There was an error getting the user`,
|
||||
data: error as any,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `Invalid user id was passed over. `,
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
if (req.body?.name !== undefined) {
|
||||
updates.name = req.body.name;
|
||||
}
|
||||
|
||||
if (req.body?.pinNumber !== undefined) {
|
||||
const existing = await db.query.scanUser.findFirst({
|
||||
where: (u, { eq }) => eq(u.pinHash, req.body.pinNumber),
|
||||
});
|
||||
|
||||
if (existing)
|
||||
return returnFunc({
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `${req.body.pinNumber} already exists please try again`,
|
||||
data: [],
|
||||
notify: false,
|
||||
room: "",
|
||||
});
|
||||
updates.pinNumber = req.body.pinNumber;
|
||||
updates.pinHash = await hashPin(req.body.pinNumber);
|
||||
}
|
||||
|
||||
if (req.body?.scannerId !== undefined) {
|
||||
updates.scannerId = req.body.scannerId;
|
||||
}
|
||||
|
||||
if (req.body?.active !== undefined) {
|
||||
updates.active = req.body.active;
|
||||
}
|
||||
|
||||
if (req.body?.role !== undefined) {
|
||||
updates.role = req.body.role;
|
||||
}
|
||||
|
||||
updates.upd_date = sql`NOW()`;
|
||||
|
||||
const updatedSetting = await db
|
||||
.update(scanUser)
|
||||
.set(updates)
|
||||
.where(eq(scanUser.id, `${id}`))
|
||||
.returning();
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "mobile",
|
||||
subModule: "user",
|
||||
message: `User ${data.name} was updated. `,
|
||||
data: updatedSetting,
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
r.delete("/user/:id", requireAuth, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db.delete(scanUser).where(eq(scanUser.id, `${id}`)),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `There was an error deleting the user`,
|
||||
data: error as any,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return apiReturn(res, {
|
||||
success: false,
|
||||
level: "error",
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: `There was no user to delete. `,
|
||||
data: [],
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "mobile",
|
||||
subModule: "user",
|
||||
message: `User was deleted. `,
|
||||
data: data ?? [],
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
21
backend/mobile/mobilePin.route.ts
Normal file
21
backend/mobile/mobilePin.route.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Router } from "express";
|
||||
import { generateUniquePin } from "../utils/generateScannerPin.utils.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.get("/new", async (_, res) => {
|
||||
const getPin = await generateUniquePin();
|
||||
|
||||
return apiReturn(res, {
|
||||
success: getPin.success,
|
||||
level: getPin.level,
|
||||
module: "mobile",
|
||||
subModule: "auth",
|
||||
message: getPin.message,
|
||||
data: getPin.data,
|
||||
status: getPin.success ? 200 : 400,
|
||||
});
|
||||
});
|
||||
|
||||
export default r;
|
||||
35
backend/mobile/scanLogs.route.ts
Normal file
35
backend/mobile/scanLogs.route.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Router } from "express";
|
||||
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { scanLog } from "../db/schema/scanlog.schema.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post("/", async (req, res) => {
|
||||
const body = req.body;
|
||||
|
||||
const newLog = await db
|
||||
.insert(scanLog)
|
||||
.values({
|
||||
scannerId: body.scannerId,
|
||||
message: body.message,
|
||||
prompt: body.prompt,
|
||||
commandDescription: body.commandDescription,
|
||||
status: body.status,
|
||||
lines: body.lines,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return apiReturn(res, {
|
||||
success: true,
|
||||
level: "info",
|
||||
module: "mobile",
|
||||
subModule: "scan logs",
|
||||
message: `New log from ${body.scannerId}`,
|
||||
data: newLog,
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
||||
28
backend/mobile/version.route.ts
Normal file
28
backend/mobile/version.route.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import fs from "node:fs";
|
||||
import { Router } from "express";
|
||||
|
||||
import path from "path";
|
||||
|
||||
const router = Router();
|
||||
|
||||
const projectRoot = path.resolve("./lstMobile"); // adjust as needed
|
||||
const appJsonPath = path.join(projectRoot, "app.json");
|
||||
|
||||
router.get("/", async (req, res) => {
|
||||
const baseUrl = `${req.protocol}://${req.get("host")}`;
|
||||
|
||||
const raw = fs.readFileSync(appJsonPath, "utf-8");
|
||||
const config = JSON.parse(raw);
|
||||
|
||||
const exp = config.expo;
|
||||
|
||||
res.json({
|
||||
packageName: exp.android?.package,
|
||||
versionName: exp.version,
|
||||
versionCode: exp.android?.versionCode,
|
||||
minSupportedVersionCode: exp?.android?.minSupportedVersionCode ?? 0,
|
||||
downloadUrl: `${baseUrl}/lst/api/mobile/apk/latest`,
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user