From 91160de4c07133cd9550fc4ebd1aa280d66fcde4 Mon Sep 17 00:00:00 2001 From: Gabriel Majeri Date: Wed, 8 May 2024 23:08:27 +0300 Subject: [PATCH] Implement script for seeding initial admin account --- package-lock.json | 1 - package.json | 2 +- prisma/schema.prisma | 3 +- prisma/seed-admin.ts | 111 +++++++++++++++++++++++++++++++++++++++++++ prisma/seed-dev.ts | 12 ----- 5 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 prisma/seed-admin.ts diff --git a/package-lock.json b/package-lock.json index 48a3b1d..a1305d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "dependencies": { "@azure/identity": "^4.0.1", - "@azure/msal-browser": "^3.10.0", "@azure/msal-node": "^2.7.0", "@hookform/resolvers": "^3.3.4", "@microsoft/microsoft-graph-client": "^3.0.7", diff --git a/package.json b/package.json index 2c47ce3..f19a6aa 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,11 @@ "prisma:push": "dotenv -e .env.development -- npx prisma db push", "prisma:migrate": "dotenv -e .env.development -- npx prisma migrate dev", "prisma:generate": "dotenv -e .env.development -- npx prisma generate", + "seed:admin": "dotenv -e .env.development -e .env.local -- ts-node --project tsconfig.seed.json prisma/seed-admin.ts", "seed:dev": "dotenv -e .env.development -- ts-node --project tsconfig.seed.json prisma/seed-dev.ts" }, "dependencies": { "@azure/identity": "^4.0.1", - "@azure/msal-browser": "^3.10.0", "@azure/msal-node": "^2.7.0", "@hookform/resolvers": "^3.3.4", "@microsoft/microsoft-graph-client": "^3.0.7", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index da9c5a6..524fae3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -11,8 +11,7 @@ datasource db { } enum Role { - appAdmin @map("app_admin") - taxesAdmin @map("taxes_admin") + appAdmin @map("app_admin") } model User { diff --git a/prisma/seed-admin.ts b/prisma/seed-admin.ts new file mode 100644 index 0000000..fa33a63 --- /dev/null +++ b/prisma/seed-admin.ts @@ -0,0 +1,111 @@ +import msal from "@azure/msal-node"; +import { + AuthProvider, + AuthProviderCallback, + Client, +} from "@microsoft/microsoft-graph-client"; +import { PrismaClient, Role } from "@prisma/client"; +import { createInterface as readlineCreateInterface } from "readline"; +import { stdin as input, stdout as output } from "process"; + +const MICROSOFT_GRAPH_ENDPOINT = "https://graph.microsoft.com/"; + +async function acquireAccessToken() { + const tenantId = process.env.AZURE_AD_TENANT_ID; + const clientId = process.env.AZURE_AD_CLIENT_ID; + const clientSecret = process.env.AZURE_AD_CLIENT_SECRET; + + if (!tenantId) { + throw Error("Entra ID tenant ID is missing"); + } + + if (!clientId) { + throw Error("Entra ID client ID is missing"); + } + + if (!clientSecret) { + throw Error("Entra ID client secret is missing"); + } + + const msalConfig = { + auth: { + authority: `https://login.microsoftonline.com/${tenantId}`, + clientId, + clientSecret, + }, + }; + + const tokenRequest = { + scopes: [`${MICROSOFT_GRAPH_ENDPOINT}/.default`], + }; + + const cca = new msal.ConfidentialClientApplication(msalConfig); + + return await cca.acquireTokenByClientCredential(tokenRequest); +} + +const prisma = new PrismaClient(); + +async function main() { + const authenticationResult = await acquireAccessToken(); + if (!authenticationResult) { + throw Error("Failed to acquire access token for MSAL API"); + } + + const { accessToken } = authenticationResult; + + const rl = readlineCreateInterface({ input, output }); + + rl.question( + "New admin user institutional e-mail address: ", + async (email: string) => { + const authProvider: AuthProvider = async ( + callback: AuthProviderCallback, + ) => callback(null, accessToken); + + const client = Client.init({ authProvider }); + + let userInfo; + try { + userInfo = await client + .api(`/users/${email}?$select=id,displayName`) + .get(); + } catch (exception) { + throw new Error( + `Failed to get user data from Microsoft 365: ${exception}`, + ); + } + + if (!userInfo || !userInfo["id"]) { + throw new Error("User with given e-mail address could not be found"); + } + + try { + const newUser = await prisma.user.create({ + data: { + azureAdObjectId: userInfo["id"], + role: Role.appAdmin, + }, + }); + + console.log( + `Successfully created admin account associated to user with ID ${newUser.azureAdObjectId}, full name ${userInfo["displayName"]}`, + ); + } catch (exception) { + console.error(`Failed to create user in database: ${exception}`); + } + + rl.close(); + }, + ); +} + +main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/prisma/seed-dev.ts b/prisma/seed-dev.ts index d957e56..6a597e6 100644 --- a/prisma/seed-dev.ts +++ b/prisma/seed-dev.ts @@ -40,18 +40,6 @@ async function main() { studentDorms.push(studentDorm); } - // Insert 5 User records - const users = []; - for (let i = 1; i <= 25; i++) { - const user = await prisma.user.create({ - data: { - azureAdObjectId: `UserObjectID${i}`, - role: i % 2 === 0 ? Role.appAdmin : Role.taxesAdmin, // Alternate between roles - }, - }); - users.push(user); - } - const facultyTaxValues = []; for (let i = 1; i <= 25; i++) { let studyCycle: StudyCycle = StudyCycle.bachelors;