Group based roles

This commit is contained in:
Douglas Barone 2023-06-19 13:44:21 -04:00
parent 431700a888
commit c05c0d8f75
7 changed files with 93 additions and 50 deletions

View File

@ -1,28 +0,0 @@
/*
Warnings:
- A unique constraint covering the columns `[serialNumber]` on the table `Printer` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE "Printer" ADD COLUMN "serialNumber" TEXT;
-- CreateTable
CREATE TABLE "PrinterStatus" (
"id" SERIAL NOT NULL,
"tonerBlackLevel" INTEGER NOT NULL,
"tonerCyanLevel" INTEGER,
"tonerMagentaLevel" INTEGER,
"tonerYellowLevel" INTEGER,
"counter" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"printerId" INTEGER NOT NULL,
CONSTRAINT "PrinterStatus_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Printer_serialNumber_key" ON "Printer"("serialNumber");
-- AddForeignKey
ALTER TABLE "PrinterStatus" ADD CONSTRAINT "PrinterStatus_printerId_fkey" FOREIGN KEY ("printerId") REFERENCES "Printer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -21,15 +21,40 @@ CREATE TABLE "User" (
-- CreateTable
CREATE TABLE "Printer" (
"id" SERIAL NOT NULL,
"hostname" TEXT,
"friendlyName" TEXT,
"serialNumber" TEXT,
"hostname" TEXT,
"ip" TEXT NOT NULL,
"model" "PrinterModel" NOT NULL,
"blackTonerModel" TEXT,
"cyanTonerModel" TEXT,
"magentaTonerModel" TEXT,
"yellowTonerModel" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Printer_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PrinterStatus" (
"id" SERIAL NOT NULL,
"tonerBlackLevel" INTEGER NOT NULL,
"tonerCyanLevel" INTEGER,
"tonerMagentaLevel" INTEGER,
"tonerYellowLevel" INTEGER,
"counter" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"printerId" INTEGER NOT NULL,
CONSTRAINT "PrinterStatus_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
-- CreateIndex
CREATE UNIQUE INDEX "Printer_serialNumber_key" ON "Printer"("serialNumber");
-- AddForeignKey
ALTER TABLE "PrinterStatus" ADD CONSTRAINT "PrinterStatus_printerId_fkey" FOREIGN KEY ("printerId") REFERENCES "Printer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -15,6 +15,7 @@ type LdapUser = {
mail: string | null
displayName: string
thumbnailPhoto: string | null
groups?: string[]
}
export class LdapController extends Client implements LdapClientInterface {
@ -45,19 +46,25 @@ export class LdapController extends Client implements LdapClientInterface {
}
}
async getUser(username: string) {
async getUser(username: string): Promise<LdapUser> {
return await this.adminBondOperation(async () => {
const { searchEntries } = await this.search(DN, {
scope: 'sub',
filter: `(sAMAccountName=${username})`,
attributes: ['mail', 'sAMAccountName', 'displayName', 'thumbnailPhoto'],
attributes: [
'mail',
'sAMAccountName',
'displayName',
'thumbnailPhoto',
'dn'
],
explicitBufferAttributes: ['thumbnailPhoto']
})
if (!searchEntries.length)
throw new Error('User not found on LDAP server.')
const { sAMAccountName, displayName, mail, thumbnailPhoto } =
const { sAMAccountName, displayName, mail, thumbnailPhoto, dn } =
searchEntries[0]
const ldapUser: LdapUser = {
@ -66,13 +73,26 @@ export class LdapController extends Client implements LdapClientInterface {
mail: mail.toString(),
thumbnailPhoto: `data:image/png;base64,${Buffer.from(
thumbnailPhoto as Buffer
).toString('base64')}`
).toString('base64')}`,
groups: await this.getGroupsForUser(dn.toString())
}
return ldapUser
})
}
async getGroupsForUser(dn: string) {
const { searchEntries } = await this.search(DN, {
scope: 'sub',
filter: `(member:1.2.840.113556.1.4.1941:=${dn})`,
attributes: ['cn']
})
if (!searchEntries.length) throw new Error('User not found on LDAP server.')
return searchEntries.map(entry => entry.cn.toString())
}
async authenticate(username: string, password: string) {
await this.bind(`${DOMAIN}\\${username}`, password)

View File

@ -1,16 +1,35 @@
import { User } from '@prisma/client'
import { LdapController } from '../controllers/LdapController.js'
import { prisma } from '../prisma.js'
const ADMIN_GROUP = process.env.ADMIN_GROUP || 'PP-SERTI'
const INSPECTOR_GROUP = process.env.INSPECTOR_GROUP || 'inspector'
const USER_GROUP = process.env.USER_GROUP || 'G_SERVIDORES'
export class UserController {
static async importUser(username: string) {
const ldap = new LdapController()
const user = await ldap.getUser(username)
const ldapUser = await ldap.getUser(username)
if (!user) throw new Error('User not found!')
if (!ldapUser) throw new Error('User not found!')
const user = {
username: ldapUser.username,
displayName: ldapUser.displayName,
mail: ldapUser.mail,
thumbnailPhoto: ldapUser.thumbnailPhoto,
roles: [] as User['roles']
}
ldapUser.groups?.forEach(group => {
if (group === USER_GROUP) user.roles?.push('USER')
if (group === ADMIN_GROUP) user.roles?.push('ADMIN')
if (group === INSPECTOR_GROUP) user.roles?.push('INSPECTOR')
})
return await prisma.user.upsert({
where: { username: user.username },
where: { username: ldapUser.username },
update: user,
create: user
})

View File

@ -0,0 +1,18 @@
import { Request, Response } from 'express'
import { AuthenticationController } from '../AuthenticationController.js'
export class LoginRouteController {
static async login(req: Request, res: Response) {
const { username, password } = req.body
if (!username || !password)
return res.status(400).json({ error: 'Missing username or password' })
try {
const token = await AuthenticationController.login(username, password)
res.json({ token })
} catch (error: any) {
res.status(401).json({ error: error.message })
}
}
}

View File

@ -13,7 +13,7 @@ export class UserRouteController {
where: { username }
})
if (!user) return await UserController.importUser(username)
if (!user) return res.json(await UserController.importUser(username))
else UserController.importUser(username)
res.json(user)

View File

@ -7,6 +7,7 @@ import { hasRolesMiddleware } from './middleware/hasRolesMiddleware.js'
import { UserRouteController } from './controllers/routes/UserRouteController.js'
import { AuthenticationController } from './controllers/AuthenticationController.js'
import { LoginRouteController } from './controllers/routes/LoginRouteController.js'
export const app = express()
@ -22,19 +23,7 @@ app.get('/api/', async (req: Request, res: Response) => {
})
// Login route
app.post('/api/login', async (req: Request, res: Response) => {
const { username, password } = req.body
if (!username || !password)
return res.status(400).json({ error: 'Missing username or password' })
try {
const token = await AuthenticationController.login(username, password)
res.json({ token })
} catch (error: any) {
res.status(401).json({ error: error.message })
}
})
app.post('/api/login', LoginRouteController.login)
app.get('/api/me', authMiddleware, async (req: Request, res: Response) =>
res.json(res.locals.user)