From d8eddafcaea9141e9413a122a320649c7dd325e5 Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Sun, 23 Mar 2025 10:07:28 -0500 Subject: [PATCH] feat(auth): add, update were added for adm account in backend only --- server/services/auth/authService.ts | 39 ++-- .../auth/controllers/userAdmin/getUsers.ts | 24 +++ .../controllers/userAdmin/updateUserAdm.ts | 68 +++++++ server/services/auth/middleware/roleCheck.ts | 85 ++++++++ server/services/auth/routes/login.ts | 143 +++++++------- server/services/auth/routes/register.ts | 163 ++++++++------- server/services/auth/routes/session.ts | 167 ++++++++-------- .../services/auth/routes/user/getUserRoles.ts | 62 ++++++ .../auth/routes/user/profileUpdate.ts | 187 ++++++++++-------- .../auth/routes/userAdmin/createUser.ts | 115 +++++++++++ .../auth/routes/userAdmin/getUsers.ts | 35 ++++ .../auth/routes/userAdmin/setUserRoles.ts | 71 +++++++ .../auth/routes/userAdmin/updateUser.ts | 91 +++++++++ .../auth/routes/userRoles/getUserRoles.ts | 53 ----- .../auth/routes/userRoles/setUserRoles.ts | 65 ------ server/types/users.ts | 15 +- 16 files changed, 941 insertions(+), 442 deletions(-) create mode 100644 server/services/auth/controllers/userAdmin/getUsers.ts create mode 100644 server/services/auth/controllers/userAdmin/updateUserAdm.ts create mode 100644 server/services/auth/middleware/roleCheck.ts create mode 100644 server/services/auth/routes/user/getUserRoles.ts create mode 100644 server/services/auth/routes/userAdmin/createUser.ts create mode 100644 server/services/auth/routes/userAdmin/getUsers.ts create mode 100644 server/services/auth/routes/userAdmin/setUserRoles.ts create mode 100644 server/services/auth/routes/userAdmin/updateUser.ts delete mode 100644 server/services/auth/routes/userRoles/getUserRoles.ts delete mode 100644 server/services/auth/routes/userRoles/setUserRoles.ts diff --git a/server/services/auth/authService.ts b/server/services/auth/authService.ts index a8282f4..5ff4dd7 100644 --- a/server/services/auth/authService.ts +++ b/server/services/auth/authService.ts @@ -1,30 +1,37 @@ -import {OpenAPIHono} from "@hono/zod-openapi"; -import {authMiddleware} from "./middleware/authMiddleware.js"; - +import { OpenAPIHono } from "@hono/zod-openapi"; import login from "./routes/login.js"; import register from "./routes/register.js"; import session from "./routes/session.js"; -import getAccess from "./routes/userRoles/getUserRoles.js"; -import setAccess from "./routes/userRoles/setUserRoles.js"; +import getAccess from "./routes/user/getUserRoles.js"; +import setAccess from "./routes/userAdmin/setUserRoles.js"; import profile from "./routes/user/profileUpdate.js"; -import {areRolesIn} from "./utils/roleCheck.js"; +import { areRolesIn } from "./utils/roleCheck.js"; +import createUser from "./routes/userAdmin/createUser.js"; +import allUsers from "./routes/userAdmin/getUsers.js"; +import updateUser from "./routes/userAdmin/updateUser.js"; const app = new OpenAPIHono(); // run the role check setTimeout(() => { - areRolesIn(); + areRolesIn(); }, 5000); -app.route("auth/login", login); -app.route("auth/register", register); -app.route("auth/session", session); +const routes = [ + login, + register, + session, + profile, + getAccess, + setAccess, + createUser, + allUsers, + updateUser, +] as const; -// required to login -/* User area just needs to be logged in to enter here */ -app.route("auth/profileupdate", profile); +// app.route("/server", modules); +const appRoutes = routes.forEach((route) => { + app.route("/auth", route); +}); -/* will need to increase to make sure the person coming here has the correct permissions */ -app.route("auth/getuseraccess", getAccess); -app.route("auth/setuseraccess", setAccess); export default app; diff --git a/server/services/auth/controllers/userAdmin/getUsers.ts b/server/services/auth/controllers/userAdmin/getUsers.ts new file mode 100644 index 0000000..5421522 --- /dev/null +++ b/server/services/auth/controllers/userAdmin/getUsers.ts @@ -0,0 +1,24 @@ +import { db } from "../../../../../database/dbclient.js"; +import { users } from "../../../../../database/schema/users.js"; +import { returnRes } from "../../../../globalUtils/routeDefs/returnRes.js"; +import { tryCatch } from "../../../../globalUtils/tryCatch.js"; +import { createLog } from "../../../logger/logger.js"; + +export const getAllUsers = async () => { + /** + * returns all users that are in lst + */ + createLog("info", "apiAuthedRoute", "auth", "Get all users"); + const { data, error } = await tryCatch(db.select().from(users)); + + if (error) { + returnRes( + false, + "There was an error getting users", + new Error("No user exists.") + ); + } + + returnRes(true, "All users.", data); + return { success: true, message: "All users", data }; +}; diff --git a/server/services/auth/controllers/userAdmin/updateUserAdm.ts b/server/services/auth/controllers/userAdmin/updateUserAdm.ts new file mode 100644 index 0000000..0d376ab --- /dev/null +++ b/server/services/auth/controllers/userAdmin/updateUserAdm.ts @@ -0,0 +1,68 @@ +import { eq } from "drizzle-orm"; +import { db } from "../../../../../database/dbclient.js"; +import { users } from "../../../../../database/schema/users.js"; +import { tryCatch } from "../../../../globalUtils/tryCatch.js"; +import type { User } from "../../../../types/users.js"; +import { createPassword } from "../../utils/createPassword.js"; +import { createLog } from "../../../logger/logger.js"; + +export const updateUserADM = async (userData: User) => { + /** + * The user model will need to be passed over so we can update per the request on the user. + * password, username, email. + */ + + createLog( + "info", + "apiAuthedRoute", + "auth", + `${userData.user_id} is being updated.` + ); + // get the orignal user info + const { data: user, error: userError } = await tryCatch( + db.select().from(users).where(eq(users.user_id, userData.user_id!)) + ); + + if (userError) { + return { + success: false, + message: "There was an error getting the user", + userError, + }; + } + if (user?.length === 0) { + return { + success: false, + message: + "The user you are looking for has either been deleted or dose not exist.", + }; + } + const upd_user = user as User; + const password: string = userData.password + ? await createPassword(userData.password!) + : upd_user.password!; + const data = { + username: userData.username ? userData.username : upd_user?.username, + password: password, + email: userData.email ? userData.email : upd_user.email, + }; + + // term ? ilike(posts.title, term) : undefined + const { data: updData, error: updError } = await tryCatch( + db.update(users).set(data).where(eq(users.user_id, userData.user_id!)) + ); + + if (updError) { + return { + success: false, + message: "There was an error getting the user", + updError, + }; + } + + return { + success: true, + message: `${userData.username} has been updated.`, + updData, + }; +}; diff --git a/server/services/auth/middleware/roleCheck.ts b/server/services/auth/middleware/roleCheck.ts new file mode 100644 index 0000000..31f07b6 --- /dev/null +++ b/server/services/auth/middleware/roleCheck.ts @@ -0,0 +1,85 @@ +import { createMiddleware } from "hono/factory"; + +import type { CustomJwtPayload } from "../../../types/jwtToken.js"; +import { verify } from "hono/jwt"; +import { db } from "../../../../database/dbclient.js"; +import { modules } from "../../../../database/schema/modules.js"; +import { and, eq } from "drizzle-orm"; +import { userRoles } from "../../../../database/schema/userRoles.js"; +import { tryCatch } from "../../../globalUtils/tryCatch.js"; + +const hasCorrectRole = (requiredRole: string[], module: string) => + createMiddleware(async (c, next) => { + /** + * We want to check to make sure you have the correct role to be here + */ + const authHeader = c.req.header("Authorization"); + + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return c.json({ error: "Unauthorized" }, 401); + } + + const token = authHeader.split(" ")[1]; + + // deal with token data + const { data: tokenData, error: tokenError } = await tryCatch( + verify(token, process.env.JWT_SECRET!) + ); + + if (tokenError) { + return c.json({ error: "Invalid token" }, 401); + } + + const customToken = tokenData as CustomJwtPayload; + + // Get the module + const { data: mod, error: modError } = await tryCatch( + db.select().from(modules).where(eq(modules.name, module)) + ); + if (modError) { + console.log(modError); + return; + } + + if (mod.length === 0) { + return c.json({ error: "You have entered an invalid module name" }, 403); + } + + // check if the user has the role needed to get into this module + const { data: userRole, error: userRoleError } = await tryCatch( + db + .select() + .from(userRoles) + .where( + and( + eq(userRoles.module_id, mod[0].module_id), + eq(userRoles.user_id, customToken.user?.user_id!) + ) + ) + ); + + if (userRoleError) { + return; + } + + if (!userRole) { + return c.json( + { + error: + "The module you are trying to access is not active or is invalid.", + }, + 403 + ); + } + + if (!requiredRole.includes(userRole[0]?.role)) { + return c.json( + { error: "You do not have access to this part of the app." }, + 403 + ); + } + + await next(); + }); + +export default hasCorrectRole; diff --git a/server/services/auth/routes/login.ts b/server/services/auth/routes/login.ts index 751361e..60bc304 100644 --- a/server/services/auth/routes/login.ts +++ b/server/services/auth/routes/login.ts @@ -1,90 +1,97 @@ -import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; -import {login} from "../controllers/login.js"; +import { z, createRoute, OpenAPIHono } from "@hono/zod-openapi"; +import { login } from "../controllers/login.js"; const app = new OpenAPIHono(); const UserSchema = z -.object({ - username: z.string().optional().openapi({example: "smith002"}), + .object({ + username: z.string().optional().openapi({ example: "smith002" }), //email: z.string().optional().openapi({example: "s.smith@example.com"}), - password: z.string().openapi({example: "password123"}), -}) -.openapi("User"); + password: z.string().openapi({ example: "password123" }), + }) + .openapi("User"); const route = createRoute({ - tags: ["Auth"], - summary: "Login as user", - description: "Login as a user to get a JWT token", - method: "post", - path: "/", - request: { - body: { - content: { - "application/json": {schema: UserSchema}, - }, - }, + tags: ["Auth"], + summary: "Login as user", + description: "Login as a user to get a JWT token", + method: "post", + path: "/login", + request: { + body: { + content: { + "application/json": { schema: UserSchema }, + }, }, - responses: { - 200: { - content: { - "application/json": { - schema: z.object({ - success: z.boolean().openapi({example: true}), - message: z.string().openapi({example: "Logged in"}), - }), - }, - }, - description: "Response message", + }, + responses: { + 200: { + content: { + "application/json": { + schema: z.object({ + success: z.boolean().openapi({ example: true }), + message: z.string().openapi({ example: "Logged in" }), + }), }, + }, + description: "Response message", + }, - 400: { - content: { - "application/json": { - schema: z.object({ - success: z.boolean().openapi({example: false}), - message: z.string().openapi({example: "Username and password required"}), - }), - }, - }, - description: "Bad request", - }, - 401: { - content: { - "application/json": { - schema: z.object({ - success: z.boolean().openapi({example: false}), - message: z.string().openapi({example: "Username and password required"}), - }), - }, - }, - description: "Bad request", + 400: { + content: { + "application/json": { + schema: z.object({ + success: z.boolean().openapi({ example: false }), + message: z + .string() + .openapi({ example: "Username and password required" }), + }), }, + }, + description: "Bad request", }, + 401: { + content: { + "application/json": { + schema: z.object({ + success: z.boolean().openapi({ example: false }), + message: z + .string() + .openapi({ example: "Username and password required" }), + }), + }, + }, + description: "Bad request", + }, + }, }); app.openapi(route, async (c) => { - const {username, password, email} = await c.req.json(); + const { username, password, email } = await c.req.json(); - if (!username || !password) { - return c.json( - { - success: false, - message: "Username and password are required", - }, - 400 - ); - } + if (!username || !password) { + return c.json( + { + success: false, + message: "Username and password are required", + }, + 400 + ); + } - try { - const {token, user} = await login(username.toLowerCase(), password); + try { + const { token, user } = await login(username.toLowerCase(), password); - // Set the JWT as an HTTP-only cookie - //c.header("Set-Cookie", `auth_token=${token}; HttpOnly; Secure; Path=/; SameSite=None; Max-Age=3600`); + // Set the JWT as an HTTP-only cookie + //c.header("Set-Cookie", `auth_token=${token}; HttpOnly; Secure; Path=/; SameSite=None; Max-Age=3600`); - return c.json({success: true, message: "Login successful", user, token}, 200); - } catch (err) { - return c.json({success: false, message: "Incorrect Credentials"}, 401); - } + return c.json( + { success: true, message: "Login successful", user, token }, + 200 + ); + } catch (err) { + return c.json({ success: false, message: "Incorrect Credentials" }, 401); + } }); export default app; diff --git a/server/services/auth/routes/register.ts b/server/services/auth/routes/register.ts index 8db2286..d9bf92e 100644 --- a/server/services/auth/routes/register.ts +++ b/server/services/auth/routes/register.ts @@ -1,97 +1,110 @@ -import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; -import {apiHit} from "../../../globalUtils/apiHits.js"; -import {registerUser} from "../controllers/register.js"; +import { z, createRoute, OpenAPIHono } from "@hono/zod-openapi"; +import { apiHit } from "../../../globalUtils/apiHits.js"; +import { registerUser } from "../controllers/register.js"; const app = new OpenAPIHono(); const UserSchema = z.object({ - username: z + username: z .string() .regex(/^[a-zA-Z0-9_]{3,30}$/) - .openapi({example: "smith034"}), - email: z.string().email().openapi({example: "smith@example.com"}), - password: z + .openapi({ example: "smith034" }), + email: z.string().email().openapi({ example: "smith@example.com" }), + password: z .string() - .min(6, {message: "Passwords must be longer than 3 characters"}) - .regex(/[A-Z]/, {message: "Password must contain at least one uppercase letter"}) - .regex(/[\W_]/, {message: "Password must contain at least one special character"}) - .openapi({example: "Password1!"}), + .min(6, { message: "Passwords must be longer than 3 characters" }) + .regex(/[A-Z]/, { + message: "Password must contain at least one uppercase letter", + }) + .regex(/[\W_]/, { + message: "Password must contain at least one special character", + }) + .openapi({ example: "Password1!" }), }); type User = z.infer; const responseSchema = z.object({ - success: z.boolean().optional().openapi({example: true}), - message: z.string().optional().openapi({example: "User Created"}), + success: z.boolean().optional().openapi({ example: true }), + message: z.string().optional().openapi({ example: "User Created" }), }); app.openapi( - createRoute({ - tags: ["Auth"], - summary: "Register a new user", - method: "post", - path: "/", - request: { - body: { - content: { - "application/json": {schema: UserSchema}, - }, - }, + createRoute({ + tags: ["Auth"], + summary: "Register a new user", + method: "post", + path: "/register", + request: { + body: { + content: { + "application/json": { schema: UserSchema }, }, - responses: { - 200: { - content: {"application/json": {schema: responseSchema}}, - description: "Retrieve the user", - }, - 400: { - content: { - "application/json": { - schema: z.object({ - success: z.boolean().openapi({example: false}), - message: z.string().openapi({example: "Invalid credentials passed"}), - }), - }, - }, - description: "Retrieve the user", - }, + }, + }, + responses: { + 200: { + content: { "application/json": { schema: responseSchema } }, + description: "Retrieve the user", + }, + 400: { + content: { + "application/json": { + schema: z.object({ + success: z.boolean().openapi({ example: false }), + message: z + .string() + .openapi({ example: "Invalid credentials passed" }), + }), + }, }, - }), - async (c) => { - // apit hit - apiHit(c, {endpoint: "api/auth/register"}); - let {username, email, password} = await c.req.json(); + description: "Retrieve the user", + }, + }, + }), + async (c) => { + // apit hit + apiHit(c, { endpoint: "api/auth/register" }); + let { username, email, password } = await c.req.json(); - if (!username || !email || !password) { - return c.json({success: false, message: "Credentials missing"}, 400); - } - - // some usernames that should be ignored - const badActors = ["admin", "root"]; - if (badActors.includes(username)) { - return c.json( - { - success: false, - message: `${username} is not a valid name to be registerd please try again`, - }, - 400 - ); - } - - try { - const register = await registerUser(username, password, email); - - return c.json({success: register.success, message: register.message, user: register?.user}, 200); - } catch (error) { - console.log(error); - return c.json( - { - success: false, - message: `${username} already exists please login or reset password, if you feel this is an error please contact your admin.`, - }, - 400 - ); - } + if (!username || !email || !password) { + return c.json({ success: false, message: "Credentials missing" }, 400); } + + // some usernames that should be ignored + const badActors = ["admin", "root"]; + if (badActors.includes(username)) { + return c.json( + { + success: false, + message: `${username} is not a valid name to be registerd please try again`, + }, + 400 + ); + } + + try { + const register = await registerUser(username, password, email); + + return c.json( + { + success: register.success, + message: register.message, + user: register?.user, + }, + 200 + ); + } catch (error) { + console.log(error); + return c.json( + { + success: false, + message: `${username} already exists please login or reset password, if you feel this is an error please contact your admin.`, + }, + 400 + ); + } + } ); export default app; diff --git a/server/services/auth/routes/session.ts b/server/services/auth/routes/session.ts index 49c0cfa..e7fd8a2 100644 --- a/server/services/auth/routes/session.ts +++ b/server/services/auth/routes/session.ts @@ -1,97 +1,110 @@ -import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; -import {verify} from "hono/jwt"; +import { z, createRoute, OpenAPIHono } from "@hono/zod-openapi"; +import { verify } from "hono/jwt"; -import {authMiddleware} from "../middleware/authMiddleware.js"; +import { authMiddleware } from "../middleware/authMiddleware.js"; import jwt from "jsonwebtoken"; const session = new OpenAPIHono(); const expiresIn = Number(process.env.JWT_EXPIRES!) || 60; const secret: string = process.env.JWT_SECRET!; -const {sign} = jwt; +const { sign } = jwt; const UserSchema = z.object({ - username: z + username: z .string() .regex(/^[a-zA-Z0-9_]{3,30}$/) - .openapi({example: "smith034"}), - email: z.string().email().openapi({example: "smith@example.com"}), - password: z + .openapi({ example: "smith034" }), + email: z.string().email().openapi({ example: "smith@example.com" }), + password: z .string() - .min(6, {message: "Passwords must be longer than 3 characters"}) - .regex(/[A-Z]/, {message: "Password must contain at least one uppercase letter"}) - .regex(/[\W_]/, {message: "Password must contain at least one special character"}) - .openapi({example: "Password1!"}), + .min(6, { message: "Passwords must be longer than 3 characters" }) + .regex(/[A-Z]/, { + message: "Password must contain at least one uppercase letter", + }) + .regex(/[\W_]/, { + message: "Password must contain at least one special character", + }) + .openapi({ example: "Password1!" }), }); session.openapi( - createRoute({ - tags: ["Auth"], - summary: "Checks a user session based on there token", - description: "Can post there via Authentiaction header or cookies", - method: "get", - path: "/", - middleware: authMiddleware, - // request: { - // body: { - // content: { - // "application/json": {schema: UserSchema}, - // }, - // }, - // }, - responses: { - 200: { - content: { - "application/json": { - schema: z.object({ - data: z.object({ - token: z.string().openapi({example: "sdkjhgsldkvhdakl;jvhs;adkjfhvds.kvnsad;ovhads"}), - // user: z.object({ - // user_id: z.string().openapi({example: "04316c86-f086-4cc6-b3d4-cca164a26f3f"}), - // username: z.string().openapi({example: "smith"}), - // email: z.string().openapi({example: "smith@example.com"}).optional(), - // }), - }), - }), - }, - }, - description: "Login successful", - }, - 401: { - content: { - "application/json": { - schema: z.object({ - message: z.string().openapi({example: "Unathenticated"}), - }), - }, - }, - description: "Error of why you were not logged in.", - }, + createRoute({ + tags: ["Auth"], + summary: "Checks a user session based on there token", + description: "Can post there via Authentiaction header or cookies", + method: "get", + path: "/session", + middleware: authMiddleware, + // request: { + // body: { + // content: { + // "application/json": {schema: UserSchema}, + // }, + // }, + // }, + responses: { + 200: { + content: { + "application/json": { + schema: z.object({ + data: z.object({ + token: z + .string() + .openapi({ + example: "sdkjhgsldkvhdakl;jvhs;adkjfhvds.kvnsad;ovhads", + }), + // user: z.object({ + // user_id: z.string().openapi({example: "04316c86-f086-4cc6-b3d4-cca164a26f3f"}), + // username: z.string().openapi({example: "smith"}), + // email: z.string().openapi({example: "smith@example.com"}).optional(), + // }), + }), + }), + }, }, - }), - async (c) => { - const authHeader = c.req.header("Authorization"); + description: "Login successful", + }, + 401: { + content: { + "application/json": { + schema: z.object({ + message: z.string().openapi({ example: "Unathenticated" }), + }), + }, + }, + description: "Error of why you were not logged in.", + }, + }, + }), + async (c) => { + const authHeader = c.req.header("Authorization"); - if (authHeader?.includes("Basic")) { - return c.json({message: "You are a Basic user! Please login to get a token"}, 401); - } - - if (!authHeader) { - return c.json({message: "Unauthorized"}, 401); - } - - const token = authHeader?.split("Bearer ")[1] || ""; - - try { - const payload = await verify(token, process.env.JWT_SECRET!); - - // If it's valid, return a new token - const newToken = sign({user: payload.user}, secret, {expiresIn: expiresIn * 60}); - - return c.json({data: {token: newToken, user: payload.user}}, 200); - } catch (error) { - return c.json({message: "Unauthorized"}, 401); - } + if (authHeader?.includes("Basic")) { + return c.json( + { message: "You are a Basic user! Please login to get a token" }, + 401 + ); } + + if (!authHeader) { + return c.json({ message: "Unauthorized" }, 401); + } + + const token = authHeader?.split("Bearer ")[1] || ""; + + try { + const payload = await verify(token, process.env.JWT_SECRET!); + + // If it's valid, return a new token + const newToken = sign({ user: payload.user }, secret, { + expiresIn: expiresIn * 60, + }); + + return c.json({ data: { token: newToken, user: payload.user } }, 200); + } catch (error) { + return c.json({ message: "Unauthorized" }, 401); + } + } ); // const token = authHeader?.split("Bearer ")[1] || ""; diff --git a/server/services/auth/routes/user/getUserRoles.ts b/server/services/auth/routes/user/getUserRoles.ts new file mode 100644 index 0000000..c756275 --- /dev/null +++ b/server/services/auth/routes/user/getUserRoles.ts @@ -0,0 +1,62 @@ +import { z, createRoute, OpenAPIHono } from "@hono/zod-openapi"; +import { apiHit } from "../../../../globalUtils/apiHits.js"; +import jwt from "jsonwebtoken"; + +import type { CustomJwtPayload } from "../../../../types/jwtToken.js"; +import { authMiddleware } from "../../middleware/authMiddleware.js"; +import hasCorrectRole from "../../middleware/roleCheck.js"; +import { roleCheck } from "../../controllers/userRoles/getUserAccess.js"; + +const { verify } = jwt; +const app = new OpenAPIHono(); + +const responseSchema = z.object({ + message: z.string().optional().openapi({ example: "User Created" }), +}); + +app.openapi( + createRoute({ + tags: ["Auth:user"], + summary: "returns the users access", + method: "get", + path: "/getAccess", + middleware: [authMiddleware], + responses: { + 200: { + content: { "application/json": { schema: responseSchema } }, + description: "Retrieve the user", + }, + }, + }), + async (c) => { + // apit hit + //apiHit(c, { endpoint: "api/auth/getUserRoles" }); + const authHeader = c.req.header("Authorization"); + const token = authHeader?.split("Bearer ")[1] || ""; + try { + const secret = process.env.JWT_SECRET!; + if (!secret) { + throw new Error("JWT_SECRET is not defined in environment variables"); + } + + const payload = verify(token, secret) as CustomJwtPayload; + + const canAccess = await roleCheck(payload.user?.user_id); + + return c.json( + { + sucess: true, + message: `User ${payload.user?.username} can access`, + data: canAccess, + }, + 200 + ); + } catch (error) { + console.log(error); + } + + return c.json({ message: "UserRoles coming over" }); + } +); + +export default app; diff --git a/server/services/auth/routes/user/profileUpdate.ts b/server/services/auth/routes/user/profileUpdate.ts index 2c7802e..a8d2576 100644 --- a/server/services/auth/routes/user/profileUpdate.ts +++ b/server/services/auth/routes/user/profileUpdate.ts @@ -1,95 +1,120 @@ -import {createRoute, OpenAPIHono, z} from "@hono/zod-openapi"; -import {authMiddleware} from "../../middleware/authMiddleware.js"; -import {updateProfile} from "../../controllers/users/updateProfile.js"; -import {verify} from "hono/jwt"; -import {createLog} from "../../../logger/logger.js"; +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; +import { authMiddleware } from "../../middleware/authMiddleware.js"; +import { updateProfile } from "../../controllers/users/updateProfile.js"; +import { verify } from "hono/jwt"; +import { createLog } from "../../../logger/logger.js"; const app = new OpenAPIHono(); const UserSchema = z.object({ - password: z + password: z .string() - .min(6, {message: "Passwords must be longer than 3 characters"}) - .regex(/[A-Z]/, {message: "Password must contain at least one uppercase letter"}) - .regex(/[\W_]/, {message: "Password must contain at least one special character"}) - .openapi({example: "Password1!"}), + .min(6, { message: "Passwords must be longer than 3 characters" }) + .regex(/[A-Z]/, { + message: "Password must contain at least one uppercase letter", + }) + .regex(/[\W_]/, { + message: "Password must contain at least one special character", + }) + .openapi({ example: "Password1!" }), }); app.openapi( - createRoute({ - tags: ["User"], - summary: "Updates a users Profile", - description: "Currently you can only update your password over the API", - method: "post", - path: "/", - middleware: authMiddleware, - request: { - body: { - content: { - "application/json": {schema: UserSchema}, - }, - }, + createRoute({ + tags: ["auth:user"], + summary: "Updates a users Profile", + description: "Currently you can only update your password over the API", + method: "post", + path: "/profile", + middleware: authMiddleware, + request: { + body: { + content: { + "application/json": { schema: UserSchema }, }, - responses: { - 200: { - content: { - "application/json": { - schema: z.object({ - message: z.string().optional().openapi({example: "User Profile has been updated"}), - }), - }, - }, - description: "Sucess return", - }, - 401: { - content: { - "application/json": { - schema: z.object({message: z.string().optional().openapi({example: "Unauthenticated"})}), - }, - }, - description: "Unauthorized", - }, - 500: { - content: { - "application/json": { - schema: z.object({message: z.string().optional().openapi({example: "Internal Server error"})}), - }, - }, - description: "Internal Server Error", - }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: z.object({ + message: z + .string() + .optional() + .openapi({ example: "User Profile has been updated" }), + }), + }, }, - }), - async (c) => { - // make sure we have a vaid user being accessed thats really logged in - const authHeader = c.req.header("Authorization"); + description: "Sucess return", + }, + 401: { + content: { + "application/json": { + schema: z.object({ + message: z + .string() + .optional() + .openapi({ example: "Unauthenticated" }), + }), + }, + }, + description: "Unauthorized", + }, + 500: { + content: { + "application/json": { + schema: z.object({ + message: z + .string() + .optional() + .openapi({ example: "Internal Server error" }), + }), + }, + }, + description: "Internal Server Error", + }, + }, + }), + async (c) => { + // make sure we have a vaid user being accessed thats really logged in + const authHeader = c.req.header("Authorization"); - if (authHeader?.includes("Basic")) { - return c.json({message: "You are a Basic user! Please login to get a token"}, 401); - } - - if (!authHeader) { - return c.json({message: "Unauthorized"}, 401); - } - - const token = authHeader?.split("Bearer ")[1] || ""; - let user; - - try { - const payload = await verify(token, process.env.JWT_SECRET!); - user = payload.user; - } catch (error) { - createLog("error", "lst", "auth", "Failed session check, user must be logged out"); - return c.json({message: "Unauthorized"}, 401); - } - - // now pass all the data over to update the user info - try { - const data = await c?.req.json(); - await updateProfile(user, data, token); - return c.json({message: "Your profile has been updated"}); - } catch (error) { - return c.json({message: "There was an error", error}); - } + if (authHeader?.includes("Basic")) { + return c.json( + { message: "You are a Basic user! Please login to get a token" }, + 401 + ); } + + if (!authHeader) { + return c.json({ message: "Unauthorized" }, 401); + } + + const token = authHeader?.split("Bearer ")[1] || ""; + let user; + + try { + const payload = await verify(token, process.env.JWT_SECRET!); + user = payload.user; + } catch (error) { + createLog( + "error", + "lst", + "auth", + "Failed session check, user must be logged out" + ); + return c.json({ message: "Unauthorized" }, 401); + } + + // now pass all the data over to update the user info + try { + const data = await c?.req.json(); + await updateProfile(user, data, token); + return c.json({ message: "Your profile has been updated" }); + } catch (error) { + return c.json({ message: "There was an error", error }); + } + } ); export default app; diff --git a/server/services/auth/routes/userAdmin/createUser.ts b/server/services/auth/routes/userAdmin/createUser.ts new file mode 100644 index 0000000..768997d --- /dev/null +++ b/server/services/auth/routes/userAdmin/createUser.ts @@ -0,0 +1,115 @@ +import { z, createRoute, OpenAPIHono } from "@hono/zod-openapi"; +import { registerUser } from "../../controllers/register.js"; +import { authMiddleware } from "../../middleware/authMiddleware.js"; +import hasCorrectRole from "../../middleware/roleCheck.js"; + +const app = new OpenAPIHono(); + +const UserSchema = z.object({ + username: z + .string() + .regex(/^[a-zA-Z0-9_]{3,30}$/) + .openapi({ example: "smith034" }), + email: z.string().email().openapi({ example: "smith@example.com" }), + password: z + .string() + .min(6, { message: "Passwords must be longer than 3 characters" }) + .regex(/[A-Z]/, { + message: "Password must contain at least one uppercase letter", + }) + .regex(/[\W_]/, { + message: "Password must contain at least one special character", + }) + .openapi({ example: "Password1!" }), +}); + +type User = z.infer; + +const responseSchema = z.object({ + success: z.boolean().optional().openapi({ example: true }), + message: z.string().optional().openapi({ example: "User Created" }), +}); + +app.openapi( + createRoute({ + tags: ["Auth:admin"], + summary: "Creates user", + method: "post", + path: "/", + middleware: [ + authMiddleware, + hasCorrectRole(["admin", "systemAdmin"], "admin"), + ], + request: { + body: { + content: { + "application/json": { schema: UserSchema }, + }, + }, + }, + responses: { + 200: { + content: { "application/json": { schema: responseSchema } }, + description: "Retrieve the user", + }, + 400: { + content: { + "application/json": { + schema: z.object({ + success: z.boolean().openapi({ example: false }), + message: z + .string() + .openapi({ example: "Invalid credentials passed" }), + }), + }, + }, + description: "Retrieve the user", + }, + }, + }), + async (c) => { + // apit hit + //apiHit(c, {endpoint: "api/auth/register"}); + let { username, email, password } = await c.req.json(); + + if (!username || !email || !password) { + return c.json({ success: false, message: "Credentials missing" }, 400); + } + + // some usernames that should be ignored + const badActors = ["admin", "root"]; + if (badActors.includes(username)) { + return c.json( + { + success: false, + message: `${username} is not a valid name to be registerd please try again`, + }, + 400 + ); + } + + try { + const register = await registerUser(username, password, email); + + return c.json( + { + success: register.success, + message: register.message, + user: register?.user, + }, + 200 + ); + } catch (error) { + console.log(error); + return c.json( + { + success: false, + message: `${username} already exists please login or reset password, if you feel this is an error please contact your admin.`, + }, + 400 + ); + } + } +); + +export default app; diff --git a/server/services/auth/routes/userAdmin/getUsers.ts b/server/services/auth/routes/userAdmin/getUsers.ts new file mode 100644 index 0000000..0520f44 --- /dev/null +++ b/server/services/auth/routes/userAdmin/getUsers.ts @@ -0,0 +1,35 @@ +import { z, createRoute, OpenAPIHono } from "@hono/zod-openapi"; + +import { responses } from "../../../../globalUtils/routeDefs/responses.js"; + +import { getAllUsers } from "../../controllers/userAdmin/getUsers.js"; +import { authMiddleware } from "../../middleware/authMiddleware.js"; +import hasCorrectRole from "../../middleware/roleCheck.js"; + +const app = new OpenAPIHono(); + +app.openapi( + createRoute({ + tags: ["Auth:admin"], + summary: "Gets Users", + method: "get", + path: "/allusers", + middleware: [ + authMiddleware, + hasCorrectRole(["admin", "systemAdmin"], "admin"), + ], + responses: responses(), + }), + async (c) => { + // apit hit + //apiHit(c, {endpoint: "api/auth/register"}); + const allUsers: any = await getAllUsers(); + return c.json({ + success: allUsers?.success, + message: allUsers?.message, + data: allUsers?.data, + }); + } +); + +export default app; diff --git a/server/services/auth/routes/userAdmin/setUserRoles.ts b/server/services/auth/routes/userAdmin/setUserRoles.ts new file mode 100644 index 0000000..6f8f190 --- /dev/null +++ b/server/services/auth/routes/userAdmin/setUserRoles.ts @@ -0,0 +1,71 @@ +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; +import { setUserAccess } from "../../controllers/userRoles/setUserRoles.js"; +import { apiHit } from "../../../../globalUtils/apiHits.js"; +import { apiReturn } from "../../../../globalUtils/apiReturn.js"; +import { authMiddleware } from "../../middleware/authMiddleware.js"; +import hasCorrectRole from "../../middleware/roleCheck.js"; +import { responses } from "../../../../globalUtils/routeDefs/responses.js"; + +const app = new OpenAPIHono(); + +const responseSchema = z.object({ + success: z.boolean().openapi({ example: true }), + message: z.string().optional().openapi({ example: "user access" }), + data: z.array(z.object({})).optional().openapi({ example: [] }), +}); + +const UserAccess = z.object({ + username: z + .string() + .regex(/^[a-zA-Z0-9_]{3,30}$/) + .openapi({ example: "smith034" }), + module: z.string().openapi({ example: "production" }), + role: z.string().openapi({ example: "viewer" }), + override: z.string().optional().openapi({ example: "secretString" }), +}); + +app.openapi( + createRoute({ + tags: ["Auth:admin"], + summary: "Sets Users access", + method: "post", + path: "/setuseraccess", + middleware: [ + authMiddleware, + hasCorrectRole(["admin", "systemAdmin"], "admin"), + ], + description: "When logged in you will be able to grant new permissions", + request: { + body: { + content: { + "application/json": { schema: UserAccess }, + }, + }, + }, + responses: responses(), + }), + async (c) => { + //apiHit(c, { endpoint: "api/auth/setUserRoles" }); + const { username, module, role, override } = await c.req.json(); + try { + const access = await setUserAccess(username, module, role, override); + //return apiReturn(c, true, access?.message, access?.data, 200); + return c.json( + { success: access.success, message: access.message, data: access.data }, + 200 + ); + } catch (error) { + console.log(error); + //return apiReturn(c, false, "Error in setting the user access", error, 400); + return c.json( + { + success: false, + message: "Error in setting the user access", + data: error, + }, + 400 + ); + } + } +); +export default app; diff --git a/server/services/auth/routes/userAdmin/updateUser.ts b/server/services/auth/routes/userAdmin/updateUser.ts new file mode 100644 index 0000000..21f322b --- /dev/null +++ b/server/services/auth/routes/userAdmin/updateUser.ts @@ -0,0 +1,91 @@ +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; +import { setUserAccess } from "../../controllers/userRoles/setUserRoles.js"; +import { apiHit } from "../../../../globalUtils/apiHits.js"; +import { apiReturn } from "../../../../globalUtils/apiReturn.js"; +import { authMiddleware } from "../../middleware/authMiddleware.js"; +import hasCorrectRole from "../../middleware/roleCheck.js"; +import { responses } from "../../../../globalUtils/routeDefs/responses.js"; +import { updateUserADM } from "../../controllers/userAdmin/updateUserAdm.js"; + +const app = new OpenAPIHono(); + +const responseSchema = z.object({ + success: z.boolean().openapi({ example: true }), + message: z.string().optional().openapi({ example: "user access" }), + data: z.array(z.object({})).optional().openapi({ example: [] }), +}); + +const UserAccess = z.object({ + user_id: z.string().openapi({ example: "users UUID" }), + username: z + .string() + .regex(/^[a-zA-Z0-9_]{3,30}$/) + .optional() + .openapi({ example: "smith034" }), + email: z + .string() + .email() + .optional() + .openapi({ example: "smith@example.com" }), + password: z + .string() + .min(6, { message: "Passwords must be longer than 3 characters" }) + .regex(/[A-Z]/, { + message: "Password must contain at least one uppercase letter", + }) + .regex(/[\W_]/, { + message: "Password must contain at least one special character", + }) + .optional() + .openapi({ example: "Password1!" }), +}); + +app.openapi( + createRoute({ + tags: ["Auth:admin"], + summary: "updates a specific user", + method: "post", + path: "/updateuser", + middleware: [ + authMiddleware, + hasCorrectRole(["admin", "systemAdmin"], "admin"), + ], + //description: "When logged in you will be able to grant new permissions", + request: { + body: { + content: { + "application/json": { schema: UserAccess }, + }, + }, + }, + responses: responses(), + }), + async (c) => { + //apiHit(c, { endpoint: "api/auth/setUserRoles" }); + const userData = await c.req.json(); + try { + const userUPD: any = await updateUserADM(userData); + //return apiReturn(c, true, access?.message, access?.data, 200); + return c.json( + { + success: userUPD.success, + message: userUPD.message, + data: userUPD.data, + }, + 200 + ); + } catch (error) { + console.log(error); + //return apiReturn(c, false, "Error in setting the user access", error, 400); + return c.json( + { + success: false, + message: "Error in setting the user access", + data: error, + }, + 400 + ); + } + } +); +export default app; diff --git a/server/services/auth/routes/userRoles/getUserRoles.ts b/server/services/auth/routes/userRoles/getUserRoles.ts deleted file mode 100644 index b3b3a69..0000000 --- a/server/services/auth/routes/userRoles/getUserRoles.ts +++ /dev/null @@ -1,53 +0,0 @@ -import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; -import {apiHit} from "../../../../globalUtils/apiHits.js"; -import jwt from "jsonwebtoken"; -import {roleCheck} from "../../controllers/userRoles/getUserAccess.js"; -import type {CustomJwtPayload} from "../../../../types/jwtToken.js"; -import {authMiddleware} from "../../middleware/authMiddleware.js"; - -const {verify} = jwt; -const app = new OpenAPIHono(); - -const responseSchema = z.object({ - message: z.string().optional().openapi({example: "User Created"}), -}); - -app.openapi( - createRoute({ - tags: ["Auth"], - summary: "Returns the useraccess table", - method: "get", - path: "/", - middleware: authMiddleware, - responses: { - 200: { - content: {"application/json": {schema: responseSchema}}, - description: "Retrieve the user", - }, - }, - }), - async (c) => { - // apit hit - apiHit(c, {endpoint: "api/auth/getUserRoles"}); - const authHeader = c.req.header("Authorization"); - const token = authHeader?.split("Bearer ")[1] || ""; - try { - const secret = process.env.JWT_SECRET!; - if (!secret) { - throw new Error("JWT_SECRET is not defined in environment variables"); - } - - const payload = verify(token, secret) as CustomJwtPayload; - - const canAccess = await roleCheck(payload.user?.user_id); - - return c.json({sucess: true, message: `User ${payload.user?.username} can access`, data: canAccess}, 200); - } catch (error) { - console.log(error); - } - - return c.json({message: "UserRoles coming over"}); - } -); - -export default app; diff --git a/server/services/auth/routes/userRoles/setUserRoles.ts b/server/services/auth/routes/userRoles/setUserRoles.ts deleted file mode 100644 index 594ab43..0000000 --- a/server/services/auth/routes/userRoles/setUserRoles.ts +++ /dev/null @@ -1,65 +0,0 @@ -import {createRoute, OpenAPIHono, z} from "@hono/zod-openapi"; -import {setUserAccess} from "../../controllers/userRoles/setUserRoles.js"; -import {apiHit} from "../../../../globalUtils/apiHits.js"; -import {apiReturn} from "../../../../globalUtils/apiReturn.js"; -import {authMiddleware} from "../../middleware/authMiddleware.js"; - -const app = new OpenAPIHono(); - -const responseSchema = z.object({ - success: z.boolean().openapi({example: true}), - message: z.string().optional().openapi({example: "user access"}), - data: z.array(z.object({})).optional().openapi({example: []}), -}); - -const UserAccess = z.object({ - username: z - .string() - .regex(/^[a-zA-Z0-9_]{3,30}$/) - .openapi({example: "smith034"}), - module: z.string().openapi({example: "production"}), - role: z.string().openapi({example: "viewer"}), - override: z.string().optional().openapi({example: "secretString"}), -}); - -app.openapi( - createRoute({ - tags: ["Auth"], - summary: "Sets Users access", - method: "post", - path: "/", - middleware: authMiddleware, - description: "When logged in you will be able to grant new permissions", - request: { - body: { - content: { - "application/json": {schema: UserAccess}, - }, - }, - }, - responses: { - 200: { - content: {"application/json": {schema: responseSchema}}, - description: "Retrieve the user", - }, - 400: { - content: {"application/json": {schema: responseSchema}}, - description: "Failed to get user access", - }, - }, - }), - async (c) => { - apiHit(c, {endpoint: "api/auth/setUserRoles"}); - const {username, module, role, override} = await c.req.json(); - try { - const access = await setUserAccess(username, module, role, override); - //return apiReturn(c, true, access?.message, access?.data, 200); - return c.json({success: access.success, message: access.message, data: access.data}, 200); - } catch (error) { - console.log(error); - //return apiReturn(c, false, "Error in setting the user access", error, 400); - return c.json({success: false, message: "Error in setting the user access", data: error}, 400); - } - } -); -export default app; diff --git a/server/types/users.ts b/server/types/users.ts index be98bc2..2f799db 100644 --- a/server/types/users.ts +++ b/server/types/users.ts @@ -1,10 +1,11 @@ -import type {Roles} from "./roles.js"; +import type { Roles } from "./roles.js"; export type User = { - user_id?: string; - email?: string; - username?: string; - roles?: Roles[]; - role?: string; - prod?: string; + user_id?: string; + email?: string; + username?: string; + roles?: Roles[]; + role?: string; + prod?: string; + password?: string; };