Skip to content

Commit 71ae71b

Browse files
authored
Merge pull request #410 from Peppermint-Lab/next
Next
2 parents 44256d1 + 5a2f929 commit 71ae71b

File tree

15 files changed

+717
-346
lines changed

15 files changed

+717
-346
lines changed

apps/api/src/controllers/auth.ts

Lines changed: 122 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import axios from "axios";
22
import bcrypt from "bcrypt";
3+
import crypto from "crypto";
34
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
45
import jwt from "jsonwebtoken";
56
import { LRUCache } from "lru-cache";
@@ -8,6 +9,7 @@ import { AuthorizationCode } from "simple-oauth2";
89
import { getOAuthProvider, getOidcConfig } from "../lib/auth";
910
import { track } from "../lib/hog";
1011
import { forgotPassword } from "../lib/nodemailer/auth/forgot-password";
12+
import { requirePermission } from "../lib/roles";
1113
import { checkSession } from "../lib/session";
1214
import { getOAuthClient } from "../lib/utils/oauth_client";
1315
import { getOidcClient } from "../lib/utils/oidc_client";
@@ -320,22 +322,31 @@ export function authRoutes(fastify: FastifyInstance) {
320322
throw new Error("Password is not valid");
321323
}
322324

323-
var b64string = process.env.SECRET;
324-
var buf = new Buffer(b64string!, "base64"); // Ta-da
325-
326-
let token = jwt.sign(
325+
// Generate a secure session token
326+
var secret = Buffer.from(process.env.SECRET!, "base64");
327+
const token = jwt.sign(
327328
{
328-
data: { id: user!.id },
329+
data: {
330+
id: user!.id,
331+
// Add a unique identifier for this session
332+
sessionId: crypto.randomBytes(32).toString("hex"),
333+
},
329334
},
330-
buf,
331-
{ expiresIn: "7d" }
335+
secret,
336+
{
337+
expiresIn: "8h",
338+
algorithm: "HS256",
339+
}
332340
);
333341

342+
// Store session with additional security info
334343
await prisma.session.create({
335344
data: {
336345
userId: user!.id,
337346
sessionToken: token,
338-
expires: new Date(Date.now() + 60 * 60 * 1000),
347+
expires: new Date(Date.now() + 8 * 60 * 60 * 1000), // 8 hours
348+
userAgent: request.headers["user-agent"] || "",
349+
ipAddress: request.ip,
339350
},
340351
});
341352

@@ -687,6 +698,9 @@ export function authRoutes(fastify: FastifyInstance) {
687698
// Delete a user
688699
fastify.delete(
689700
"/api/v1/auth/user/:id",
701+
{
702+
preHandler: requirePermission(["user::delete"]),
703+
},
690704
async (request: FastifyRequest, reply: FastifyReply) => {
691705
const { id } = request.params as { id: string };
692706

@@ -763,18 +777,12 @@ export function authRoutes(fastify: FastifyInstance) {
763777
password: string;
764778
};
765779

766-
const bearer = request.headers.authorization!.split(" ")[1];
767-
768-
let session = await prisma.session.findUnique({
769-
where: {
770-
sessionToken: bearer,
771-
},
772-
});
780+
const session = await checkSession(request);
773781

774782
const hashedPass = await bcrypt.hash(password, 10);
775783

776784
await prisma.user.update({
777-
where: { id: session?.userId },
785+
where: { id: session?.id },
778786
data: {
779787
password: hashedPass,
780788
},
@@ -789,6 +797,9 @@ export function authRoutes(fastify: FastifyInstance) {
789797
// Reset password by admin
790798
fastify.post(
791799
"/api/v1/auth/admin/reset-password",
800+
{
801+
preHandler: requirePermission(["user::manage"]),
802+
},
792803
async (request: FastifyRequest, reply: FastifyReply) => {
793804
let { password, user } = request.body as {
794805
password: string;
@@ -830,14 +841,11 @@ export function authRoutes(fastify: FastifyInstance) {
830841
// Update a users profile/config
831842
fastify.put(
832843
"/api/v1/auth/profile",
844+
{
845+
preHandler: requirePermission(["user::update"]),
846+
},
833847
async (request: FastifyRequest, reply: FastifyReply) => {
834-
const bearer = request.headers.authorization!.split(" ")[1];
835-
836-
let session = await prisma.session.findUnique({
837-
where: {
838-
sessionToken: bearer,
839-
},
840-
});
848+
const session = await checkSession(request);
841849

842850
const { name, email, language } = request.body as {
843851
name: string;
@@ -846,7 +854,7 @@ export function authRoutes(fastify: FastifyInstance) {
846854
};
847855

848856
let user = await prisma.user.update({
849-
where: { id: session?.userId },
857+
where: { id: session?.id },
850858
data: {
851859
name: name,
852860
email: email,
@@ -863,13 +871,11 @@ export function authRoutes(fastify: FastifyInstance) {
863871
// Update a users Email notification settings
864872
fastify.put(
865873
"/api/v1/auth/profile/notifcations/emails",
874+
{
875+
preHandler: requirePermission(["user::update"]),
876+
},
866877
async (request: FastifyRequest, reply: FastifyReply) => {
867-
const bearer = request.headers.authorization!.split(" ")[1];
868-
let session = await prisma.session.findUnique({
869-
where: {
870-
sessionToken: bearer,
871-
},
872-
});
878+
const session = await checkSession(request);
873879

874880
const {
875881
notify_ticket_created,
@@ -879,7 +885,7 @@ export function authRoutes(fastify: FastifyInstance) {
879885
} = request.body as any;
880886

881887
let user = await prisma.user.update({
882-
where: { id: session?.userId },
888+
where: { id: session?.id },
883889
data: {
884890
notify_ticket_created: notify_ticket_created,
885891
notify_ticket_assigned: notify_ticket_assigned,
@@ -911,29 +917,40 @@ export function authRoutes(fastify: FastifyInstance) {
911917
// Update a users role
912918
fastify.put(
913919
"/api/v1/auth/user/role",
920+
{
921+
preHandler: requirePermission(["user::manage"]),
922+
},
914923
async (request: FastifyRequest, reply: FastifyReply) => {
915-
const { id, role } = request.body as { id: string; role: boolean };
916-
// check for atleast one admin on role downgrade
917-
if (role === false) {
918-
const admins = await prisma.user.findMany({
919-
where: { isAdmin: true },
920-
});
921-
if (admins.length === 1) {
922-
reply.code(400).send({
923-
message: "Atleast one admin is required",
924-
success: false,
924+
const session = await checkSession(request);
925+
926+
if (session?.isAdmin) {
927+
const { id, role } = request.body as { id: string; role: boolean };
928+
if (role === false) {
929+
const admins = await prisma.user.findMany({
930+
where: { isAdmin: true },
925931
});
926-
return;
932+
if (admins.length === 1) {
933+
reply.code(400).send({
934+
message: "Atleast one admin is required",
935+
success: false,
936+
});
937+
return;
938+
}
927939
}
928-
}
929-
await prisma.user.update({
930-
where: { id },
931-
data: {
932-
isAdmin: role,
933-
},
934-
});
940+
await prisma.user.update({
941+
where: { id },
942+
data: {
943+
isAdmin: role,
944+
},
945+
});
935946

936-
reply.send({ success: true });
947+
reply.send({ success: true });
948+
} else {
949+
reply.code(401).send({
950+
message: "Unauthorized",
951+
success: false,
952+
});
953+
}
937954
}
938955
);
939956

@@ -955,4 +972,59 @@ export function authRoutes(fastify: FastifyInstance) {
955972
reply.send({ success: true });
956973
}
957974
);
975+
976+
// Add a new endpoint to list and manage active sessions
977+
fastify.get(
978+
"/api/v1/auth/sessions",
979+
async (request: FastifyRequest, reply: FastifyReply) => {
980+
const currentUser = await checkSession(request);
981+
if (!currentUser) {
982+
return reply.code(401).send({ message: "Unauthorized" });
983+
}
984+
985+
const sessions = await prisma.session.findMany({
986+
where: { userId: currentUser.id },
987+
select: {
988+
id: true,
989+
userAgent: true,
990+
ipAddress: true,
991+
createdAt: true,
992+
expires: true,
993+
},
994+
});
995+
996+
reply.send({ sessions });
997+
}
998+
);
999+
1000+
// Add ability to revoke specific sessions
1001+
fastify.delete(
1002+
"/api/v1/auth/sessions/:sessionId",
1003+
async (request: FastifyRequest, reply: FastifyReply) => {
1004+
const currentUser = await checkSession(request);
1005+
if (!currentUser) {
1006+
return reply.code(401).send({ message: "Unauthorized" });
1007+
}
1008+
1009+
const { sessionId } = request.params as { sessionId: string };
1010+
1011+
// Only allow users to delete their own sessions
1012+
const session = await prisma.session.findFirst({
1013+
where: {
1014+
id: sessionId,
1015+
userId: currentUser.id,
1016+
},
1017+
});
1018+
1019+
if (!session) {
1020+
return reply.code(404).send({ message: "Session not found" });
1021+
}
1022+
1023+
await prisma.session.delete({
1024+
where: { id: sessionId },
1025+
});
1026+
1027+
reply.send({ success: true });
1028+
}
1029+
);
9581030
}

apps/api/src/controllers/config.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,4 +384,27 @@ export function configRoutes(fastify: FastifyInstance) {
384384
});
385385
}
386386
);
387+
388+
// Toggle all roles
389+
fastify.patch(
390+
"/api/v1/config/toggle-roles",
391+
392+
async (request: FastifyRequest, reply: FastifyReply) => {
393+
const { isActive }: any = request.body;
394+
395+
const config = await prisma.config.findFirst();
396+
397+
await prisma.config.update({
398+
where: { id: config!.id },
399+
data: {
400+
roles_active: isActive,
401+
},
402+
});
403+
404+
reply.send({
405+
success: true,
406+
message: "Roles updated!",
407+
});
408+
}
409+
);
387410
}

0 commit comments

Comments
 (0)