From e76caa68f15078e244f90edb49dfc57760c7cdf0 Mon Sep 17 00:00:00 2001 From: Jackson Roberts Date: Sun, 25 Jan 2026 15:03:17 -0600 Subject: [PATCH] User Authentication Flow Works --- .docker/docker-compose.yml | 17 +-- bun.lock | 12 +++ generated/prisma/commonInputTypes.ts | 54 ---------- generated/prisma/internal/class.ts | 4 +- generated/prisma/internal/prismaNamespace.ts | 14 --- generated/prisma/models/User.ts | 102 +++++------------- package.json | 2 + .../migrations/20260125205653/migration.sql | 76 +++++++++++++ prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 2 +- src/api/.gitignore | 5 - src/api/auth/redirect.ts | 36 +++++++ src/api/prisma.config.ts | 12 --- src/api/server.ts | 1 + src/constants.ts | 38 ++++--- src/controllers/UserController.ts | 21 +--- src/managers/roles.ts | 18 ++-- src/managers/sessions.ts | 2 - src/managers/users.ts | 51 ++++----- src/modules/fetchMicrosoftUser.ts | 33 ++++++ src/types/MSAuthTypes.ts | 14 +++ tsconfig.json | 6 +- 22 files changed, 275 insertions(+), 248 deletions(-) create mode 100644 prisma/migrations/20260125205653/migration.sql create mode 100644 prisma/migrations/migration_lock.toml delete mode 100644 src/api/.gitignore create mode 100644 src/api/auth/redirect.ts delete mode 100644 src/api/prisma.config.ts create mode 100644 src/modules/fetchMicrosoftUser.ts create mode 100644 src/types/MSAuthTypes.ts diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml index 1ac86aa..541011b 100644 --- a/.docker/docker-compose.yml +++ b/.docker/docker-compose.yml @@ -2,27 +2,16 @@ version: "3" services: pgsql: - image: postgres + image: postgres:17 restart: unless-stopped environment: - POSTGRES_USER: duxcore + POSTGRES_USER: ttscm POSTGRES_PASSWORD: 123web123 - POSTGRES_DB: duxcore + POSTGRES_DB: ttscm volumes: - ./postgres:/var/lib/postgresql/data ports: - 5432:5432 - pgsql-boss: - image: postgres - restart: unless-stopped - environment: - POSTGRES_USER: pg-boss - POSTGRES_PASSWORD: 123web123 - POSTGRES_DB: pg-boss - volumes: - - ./postgres-pgb:/var/lib/postgresql/data - ports: - - 2345:5432 redis: image: redis:6.2-alpine diff --git a/bun.lock b/bun.lock index 064769a..473143e 100644 --- a/bun.lock +++ b/bun.lock @@ -5,9 +5,11 @@ "": { "name": "ttscm-api", "dependencies": { + "@azure/msal-node": "^5.0.2", "@discordjs/collection": "^2.1.1", "@duxcore/eventra": "^1.1.0", "@prisma/adapter-pg": "^7.3.0", + "@prisma/client": "^7.3.0", "cors": "^2.8.6", "cuid": "^3.0.0", "hono": "^4.11.5", @@ -27,6 +29,10 @@ }, }, "packages": { + "@azure/msal-common": ["@azure/msal-common@16.0.2", "", {}, "sha512-ZJ/UR7lyqIntURrIJCyvScwJFanM9QhJYcJCheB21jZofGKpP9QxWgvADANo7UkresHKzV+6YwoeZYP7P7HvUg=="], + + "@azure/msal-node": ["@azure/msal-node@5.0.2", "", { "dependencies": { "@azure/msal-common": "16.0.2", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-3tHeJghckgpTX98TowJoXOjKGuds0L+FKfeHJtoZFl2xvwE6RF65shZJzMQ5EQZWXzh3sE1i9gE+m3aRMachjA=="], + "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@10.5.0", "", { "dependencies": { "@chevrotain/gast": "10.5.0", "@chevrotain/types": "10.5.0", "lodash": "4.17.21" } }, "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw=="], "@chevrotain/gast": ["@chevrotain/gast@10.5.0", "", { "dependencies": { "@chevrotain/types": "10.5.0", "lodash": "4.17.21" } }, "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A=="], @@ -51,6 +57,10 @@ "@prisma/adapter-pg": ["@prisma/adapter-pg@7.3.0", "", { "dependencies": { "@prisma/driver-adapter-utils": "7.3.0", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "sha512-iuYQMbIPO6i9O45Fv8TB7vWu00BXhCaNAShenqF7gLExGDbnGp5BfFB4yz1K59zQ59jF6tQ9YHrg0P6/J3OoLg=="], + "@prisma/client": ["@prisma/client@7.3.0", "", { "dependencies": { "@prisma/client-runtime-utils": "7.3.0" }, "peerDependencies": { "prisma": "*", "typescript": ">=5.4.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-FXBIxirqQfdC6b6HnNgxGmU7ydCPEPk7maHMOduJJfnTP+MuOGa15X4omjR/zpPUUpm8ef/mEFQjJudOGkXFcQ=="], + + "@prisma/client-runtime-utils": ["@prisma/client-runtime-utils@7.3.0", "", {}, "sha512-dG/ceD9c+tnXATPk8G+USxxYM9E6UdMTnQeQ+1SZUDxTz7SgQcfxEqafqIQHcjdlcNK/pvmmLfSwAs3s2gYwUw=="], + "@prisma/config": ["@prisma/config@7.3.0", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-QyMV67+eXF7uMtKxTEeQqNu/Be7iH+3iDZOQZW5ttfbSwBamCSdwPszA0dum+Wx27I7anYTPLmRmMORKViSW1A=="], "@prisma/debug": ["@prisma/debug@7.3.0", "", {}, "sha512-yh/tHhraCzYkffsI1/3a7SHX8tpgbJu1NPnuxS4rEpJdWAUDHUH25F1EDo6PPzirpyLNkgPPZdhojQK804BGtg=="], @@ -281,6 +291,8 @@ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + "valibot": ["valibot@1.2.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg=="], "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], diff --git a/generated/prisma/commonInputTypes.ts b/generated/prisma/commonInputTypes.ts index 2da8eda..feb939f 100644 --- a/generated/prisma/commonInputTypes.ts +++ b/generated/prisma/commonInputTypes.ts @@ -130,17 +130,6 @@ export type StringNullableFilter<$PrismaModel = never> = { not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null } -export type IntFilter<$PrismaModel = never> = { - equals?: number | Prisma.IntFieldRefInput<$PrismaModel> - in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> - notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> - lt?: number | Prisma.IntFieldRefInput<$PrismaModel> - lte?: number | Prisma.IntFieldRefInput<$PrismaModel> - gt?: number | Prisma.IntFieldRefInput<$PrismaModel> - gte?: number | Prisma.IntFieldRefInput<$PrismaModel> - not?: Prisma.NestedIntFilter<$PrismaModel> | number -} - export type StringNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null @@ -159,22 +148,6 @@ export type StringNullableWithAggregatesFilter<$PrismaModel = never> = { _max?: Prisma.NestedStringNullableFilter<$PrismaModel> } -export type IntWithAggregatesFilter<$PrismaModel = never> = { - equals?: number | Prisma.IntFieldRefInput<$PrismaModel> - in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> - notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> - lt?: number | Prisma.IntFieldRefInput<$PrismaModel> - lte?: number | Prisma.IntFieldRefInput<$PrismaModel> - gt?: number | Prisma.IntFieldRefInput<$PrismaModel> - gte?: number | Prisma.IntFieldRefInput<$PrismaModel> - not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number - _count?: Prisma.NestedIntFilter<$PrismaModel> - _avg?: Prisma.NestedFloatFilter<$PrismaModel> - _sum?: Prisma.NestedIntFilter<$PrismaModel> - _min?: Prisma.NestedIntFilter<$PrismaModel> - _max?: Prisma.NestedIntFilter<$PrismaModel> -} - export type NestedStringFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> @@ -322,31 +295,4 @@ export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = { _max?: Prisma.NestedStringNullableFilter<$PrismaModel> } -export type NestedIntWithAggregatesFilter<$PrismaModel = never> = { - equals?: number | Prisma.IntFieldRefInput<$PrismaModel> - in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> - notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> - lt?: number | Prisma.IntFieldRefInput<$PrismaModel> - lte?: number | Prisma.IntFieldRefInput<$PrismaModel> - gt?: number | Prisma.IntFieldRefInput<$PrismaModel> - gte?: number | Prisma.IntFieldRefInput<$PrismaModel> - not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number - _count?: Prisma.NestedIntFilter<$PrismaModel> - _avg?: Prisma.NestedFloatFilter<$PrismaModel> - _sum?: Prisma.NestedIntFilter<$PrismaModel> - _min?: Prisma.NestedIntFilter<$PrismaModel> - _max?: Prisma.NestedIntFilter<$PrismaModel> -} - -export type NestedFloatFilter<$PrismaModel = never> = { - equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> - in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> - notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> - lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> - lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> - gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> - gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> - not?: Prisma.NestedFloatFilter<$PrismaModel> | number -} - diff --git a/generated/prisma/internal/class.ts b/generated/prisma/internal/class.ts index f9cf156..9cce4d3 100644 --- a/generated/prisma/internal/class.ts +++ b/generated/prisma/internal/class.ts @@ -20,7 +20,7 @@ const config: runtime.GetPrismaClientConfig = { "clientVersion": "7.3.0", "engineVersion": "9d6ad21cbbceab97458517b147a6a09ff43aa735", "activeProvider": "postgresql", - "inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\n// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?\n// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nmodel Session {\n id String @id @default(uuid())\n sessionKey String @unique @default(cuid())\n userId String\n expires DateTime\n refreshTokenGenerated Boolean @default(false)\n refreshedAt DateTime?\n invalidatedAt DateTime?\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel User {\n id String @id @default(cuid())\n roles Role[]\n permissions String?\n login String @unique\n name String?\n email String @unique\n emailVerified DateTime?\n image String?\n\n userId Int @unique\n token String?\n\n sessions Session[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Role {\n id String @id @default(uuid())\n title String\n moniker String @unique // e.g. admin, super_admin, moderator\n\n permissions String\n users User[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n", + "inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\n// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?\n// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nmodel Session {\n id String @id @default(uuid())\n sessionKey String @unique @default(cuid())\n userId String\n expires DateTime\n refreshTokenGenerated Boolean @default(false)\n refreshedAt DateTime?\n invalidatedAt DateTime?\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel User {\n id String @id @default(cuid())\n roles Role[]\n permissions String?\n login String @unique\n name String?\n email String @unique\n emailVerified DateTime?\n image String?\n\n userId String @unique\n token String?\n\n sessions Session[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Role {\n id String @id @default(uuid())\n title String\n moniker String @unique // e.g. admin, super_admin, moderator\n\n permissions String\n users User[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n", "runtimeDataModel": { "models": {}, "enums": {}, @@ -28,7 +28,7 @@ const config: runtime.GetPrismaClientConfig = { } } -config.runtimeDataModel = JSON.parse("{\"models\":{\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessionKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expires\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"refreshTokenGenerated\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"refreshedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"invalidatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"roles\",\"kind\":\"object\",\"type\":\"Role\",\"relationName\":\"RoleToUser\"},{\"name\":\"permissions\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"login\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailVerified\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"image\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessions\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Role\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"moniker\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"permissions\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"users\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"RoleToUser\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessionKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expires\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"refreshTokenGenerated\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"refreshedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"invalidatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"roles\",\"kind\":\"object\",\"type\":\"Role\",\"relationName\":\"RoleToUser\"},{\"name\":\"permissions\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"login\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailVerified\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"image\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessions\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Role\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"moniker\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"permissions\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"users\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"RoleToUser\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") async function decodeBase64AsWasm(wasmBase64: string): Promise { const { Buffer } = await import('node:buffer') diff --git a/generated/prisma/internal/prismaNamespace.ts b/generated/prisma/internal/prismaNamespace.ts index 9bf4a22..6fdc93d 100644 --- a/generated/prisma/internal/prismaNamespace.ts +++ b/generated/prisma/internal/prismaNamespace.ts @@ -787,20 +787,6 @@ export type IntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'In export type ListIntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Int[]'> - -/** - * Reference to a field of type 'Float' - */ -export type FloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float'> - - - -/** - * Reference to a field of type 'Float[]' - */ -export type ListFloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float[]'> - - /** * Batch Payload for updateMany & deleteMany & createMany */ diff --git a/generated/prisma/models/User.ts b/generated/prisma/models/User.ts index e3a66e2..7f38da9 100644 --- a/generated/prisma/models/User.ts +++ b/generated/prisma/models/User.ts @@ -20,20 +20,10 @@ export type UserModel = runtime.Types.Result.DefaultSelection | string emailVerified?: Prisma.DateTimeNullableFilter<"User"> | Date | string | null image?: Prisma.StringNullableFilter<"User"> | string | null - userId?: Prisma.IntFilter<"User"> | number + userId?: Prisma.StringFilter<"User"> | string token?: Prisma.StringNullableFilter<"User"> | string | null createdAt?: Prisma.DateTimeFilter<"User"> | Date | string updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string @@ -288,7 +254,7 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{ id?: string login?: string email?: string - userId?: number + userId?: string AND?: Prisma.UserWhereInput | Prisma.UserWhereInput[] OR?: Prisma.UserWhereInput[] NOT?: Prisma.UserWhereInput | Prisma.UserWhereInput[] @@ -316,10 +282,8 @@ export type UserOrderByWithAggregationInput = { createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder _count?: Prisma.UserCountOrderByAggregateInput - _avg?: Prisma.UserAvgOrderByAggregateInput _max?: Prisma.UserMaxOrderByAggregateInput _min?: Prisma.UserMinOrderByAggregateInput - _sum?: Prisma.UserSumOrderByAggregateInput } export type UserScalarWhereWithAggregatesInput = { @@ -333,7 +297,7 @@ export type UserScalarWhereWithAggregatesInput = { email?: Prisma.StringWithAggregatesFilter<"User"> | string emailVerified?: Prisma.DateTimeNullableWithAggregatesFilter<"User"> | Date | string | null image?: Prisma.StringNullableWithAggregatesFilter<"User"> | string | null - userId?: Prisma.IntWithAggregatesFilter<"User"> | number + userId?: Prisma.StringWithAggregatesFilter<"User"> | string token?: Prisma.StringNullableWithAggregatesFilter<"User"> | string | null createdAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string updatedAt?: Prisma.DateTimeWithAggregatesFilter<"User"> | Date | string @@ -347,7 +311,7 @@ export type UserCreateInput = { email: string emailVerified?: Date | string | null image?: string | null - userId: number + userId: string token?: string | null createdAt?: Date | string updatedAt?: Date | string @@ -363,7 +327,7 @@ export type UserUncheckedCreateInput = { email: string emailVerified?: Date | string | null image?: string | null - userId: number + userId: string token?: string | null createdAt?: Date | string updatedAt?: Date | string @@ -379,7 +343,7 @@ export type UserUpdateInput = { email?: Prisma.StringFieldUpdateOperationsInput | string emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null - userId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.StringFieldUpdateOperationsInput | string token?: Prisma.NullableStringFieldUpdateOperationsInput | string | null createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -395,7 +359,7 @@ export type UserUncheckedUpdateInput = { email?: Prisma.StringFieldUpdateOperationsInput | string emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null - userId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.StringFieldUpdateOperationsInput | string token?: Prisma.NullableStringFieldUpdateOperationsInput | string | null createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -411,7 +375,7 @@ export type UserCreateManyInput = { email: string emailVerified?: Date | string | null image?: string | null - userId: number + userId: string token?: string | null createdAt?: Date | string updatedAt?: Date | string @@ -425,7 +389,7 @@ export type UserUpdateManyMutationInput = { email?: Prisma.StringFieldUpdateOperationsInput | string emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null - userId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.StringFieldUpdateOperationsInput | string token?: Prisma.NullableStringFieldUpdateOperationsInput | string | null createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -439,7 +403,7 @@ export type UserUncheckedUpdateManyInput = { email?: Prisma.StringFieldUpdateOperationsInput | string emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null - userId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.StringFieldUpdateOperationsInput | string token?: Prisma.NullableStringFieldUpdateOperationsInput | string | null createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -464,10 +428,6 @@ export type UserCountOrderByAggregateInput = { updatedAt?: Prisma.SortOrder } -export type UserAvgOrderByAggregateInput = { - userId?: Prisma.SortOrder -} - export type UserMaxOrderByAggregateInput = { id?: Prisma.SortOrder permissions?: Prisma.SortOrder @@ -496,10 +456,6 @@ export type UserMinOrderByAggregateInput = { updatedAt?: Prisma.SortOrder } -export type UserSumOrderByAggregateInput = { - userId?: Prisma.SortOrder -} - export type UserListRelationFilter = { every?: Prisma.UserWhereInput some?: Prisma.UserWhereInput @@ -528,14 +484,6 @@ export type NullableStringFieldUpdateOperationsInput = { set?: string | null } -export type IntFieldUpdateOperationsInput = { - set?: number - increment?: number - decrement?: number - multiply?: number - divide?: number -} - export type UserCreateNestedManyWithoutRolesInput = { create?: Prisma.XOR | Prisma.UserCreateWithoutRolesInput[] | Prisma.UserUncheckedCreateWithoutRolesInput[] connectOrCreate?: Prisma.UserCreateOrConnectWithoutRolesInput | Prisma.UserCreateOrConnectWithoutRolesInput[] @@ -582,7 +530,7 @@ export type UserCreateWithoutSessionsInput = { email: string emailVerified?: Date | string | null image?: string | null - userId: number + userId: string token?: string | null createdAt?: Date | string updatedAt?: Date | string @@ -597,7 +545,7 @@ export type UserUncheckedCreateWithoutSessionsInput = { email: string emailVerified?: Date | string | null image?: string | null - userId: number + userId: string token?: string | null createdAt?: Date | string updatedAt?: Date | string @@ -628,7 +576,7 @@ export type UserUpdateWithoutSessionsInput = { email?: Prisma.StringFieldUpdateOperationsInput | string emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null - userId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.StringFieldUpdateOperationsInput | string token?: Prisma.NullableStringFieldUpdateOperationsInput | string | null createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -643,7 +591,7 @@ export type UserUncheckedUpdateWithoutSessionsInput = { email?: Prisma.StringFieldUpdateOperationsInput | string emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null - userId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.StringFieldUpdateOperationsInput | string token?: Prisma.NullableStringFieldUpdateOperationsInput | string | null createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -658,7 +606,7 @@ export type UserCreateWithoutRolesInput = { email: string emailVerified?: Date | string | null image?: string | null - userId: number + userId: string token?: string | null createdAt?: Date | string updatedAt?: Date | string @@ -673,7 +621,7 @@ export type UserUncheckedCreateWithoutRolesInput = { email: string emailVerified?: Date | string | null image?: string | null - userId: number + userId: string token?: string | null createdAt?: Date | string updatedAt?: Date | string @@ -712,7 +660,7 @@ export type UserScalarWhereInput = { email?: Prisma.StringFilter<"User"> | string emailVerified?: Prisma.DateTimeNullableFilter<"User"> | Date | string | null image?: Prisma.StringNullableFilter<"User"> | string | null - userId?: Prisma.IntFilter<"User"> | number + userId?: Prisma.StringFilter<"User"> | string token?: Prisma.StringNullableFilter<"User"> | string | null createdAt?: Prisma.DateTimeFilter<"User"> | Date | string updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string @@ -726,7 +674,7 @@ export type UserUpdateWithoutRolesInput = { email?: Prisma.StringFieldUpdateOperationsInput | string emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null - userId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.StringFieldUpdateOperationsInput | string token?: Prisma.NullableStringFieldUpdateOperationsInput | string | null createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -741,7 +689,7 @@ export type UserUncheckedUpdateWithoutRolesInput = { email?: Prisma.StringFieldUpdateOperationsInput | string emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null - userId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.StringFieldUpdateOperationsInput | string token?: Prisma.NullableStringFieldUpdateOperationsInput | string | null createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -756,7 +704,7 @@ export type UserUncheckedUpdateManyWithoutRolesInput = { email?: Prisma.StringFieldUpdateOperationsInput | string emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null - userId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.StringFieldUpdateOperationsInput | string token?: Prisma.NullableStringFieldUpdateOperationsInput | string | null createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -884,7 +832,7 @@ export type $UserPayload readonly emailVerified: Prisma.FieldRef<"User", 'DateTime'> readonly image: Prisma.FieldRef<"User", 'String'> - readonly userId: Prisma.FieldRef<"User", 'Int'> + readonly userId: Prisma.FieldRef<"User", 'String'> readonly token: Prisma.FieldRef<"User", 'String'> readonly createdAt: Prisma.FieldRef<"User", 'DateTime'> readonly updatedAt: Prisma.FieldRef<"User", 'DateTime'> diff --git a/package.json b/package.json index 595cc18..7e727de 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,11 @@ "utils:gen_private_keys": "bun ./utils/genPrivateKeys" }, "dependencies": { + "@azure/msal-node": "^5.0.2", "@discordjs/collection": "^2.1.1", "@duxcore/eventra": "^1.1.0", "@prisma/adapter-pg": "^7.3.0", + "@prisma/client": "^7.3.0", "cors": "^2.8.6", "cuid": "^3.0.0", "hono": "^4.11.5", diff --git a/prisma/migrations/20260125205653/migration.sql b/prisma/migrations/20260125205653/migration.sql new file mode 100644 index 0000000..87b80bd --- /dev/null +++ b/prisma/migrations/20260125205653/migration.sql @@ -0,0 +1,76 @@ +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "sessionKey" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL, + "refreshTokenGenerated" BOOLEAN NOT NULL DEFAULT false, + "refreshedAt" TIMESTAMP(3), + "invalidatedAt" TIMESTAMP(3), + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "permissions" TEXT, + "login" TEXT NOT NULL, + "name" TEXT, + "email" TEXT NOT NULL, + "emailVerified" TIMESTAMP(3), + "image" TEXT, + "userId" TEXT NOT NULL, + "token" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Role" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "moniker" TEXT NOT NULL, + "permissions" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Role_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_RoleToUser" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + + CONSTRAINT "_RoleToUser_AB_pkey" PRIMARY KEY ("A","B") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_sessionKey_key" ON "Session"("sessionKey"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_login_key" ON "User"("login"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_userId_key" ON "User"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Role_moniker_key" ON "Role"("moniker"); + +-- CreateIndex +CREATE INDEX "_RoleToUser_B_index" ON "_RoleToUser"("B"); + +-- AddForeignKey +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_RoleToUser" ADD CONSTRAINT "_RoleToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Role"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_RoleToUser" ADD CONSTRAINT "_RoleToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 092ebc4..37a11be 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -34,7 +34,7 @@ model User { emailVerified DateTime? image String? - userId Int @unique + userId String @unique token String? sessions Session[] diff --git a/src/api/.gitignore b/src/api/.gitignore deleted file mode 100644 index 9f62ec0..0000000 --- a/src/api/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -# Keep environment variables out of version control -.env - -/generated/prisma diff --git a/src/api/auth/redirect.ts b/src/api/auth/redirect.ts new file mode 100644 index 0000000..bebac0b --- /dev/null +++ b/src/api/auth/redirect.ts @@ -0,0 +1,36 @@ +import { Hono } from "hono/tiny"; +import { createRoute } from "../../modules/api-utils/createRoute"; +import * as msal from "@azure/msal-node"; +import { msalClient } from "../../constants"; +import { users } from "../../managers/users"; + +/* /v1/authRedirect */ +export default createRoute("get", ["/"], async (c) => { + c.status(200); + + console.log("Query", c.req.query()); + + const tokenRequest: msal.AuthorizationCodeRequest = { + code: c.req.query().code as string, + scopes: ["user.read"], + redirectUri: "http://localhost:3000/v1/auth/redirect", + }; + + const authResult = await msalClient.acquireTokenByCode(tokenRequest); + + await users.authenticate(authResult); + + // This closes the window because duh + return c.html(` + + `); + + /* return c.json({ + status: 200, + message: "Auth Redirect Endpoint", + data: authResult, + successful: true, + }); */ +}); diff --git a/src/api/prisma.config.ts b/src/api/prisma.config.ts deleted file mode 100644 index d5ecde5..0000000 --- a/src/api/prisma.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -// This file was generated by Prisma, and assumes you run Prisma commands using `bun --bun run prisma [command]`. -import { defineConfig, env } from "prisma/config"; - -export default defineConfig({ - schema: "prisma/schema.prisma", - migrations: { - path: "prisma/migrations", - }, - datasource: { - url: env("DATABASE_URL"), - }, -}); diff --git a/src/api/server.ts b/src/api/server.ts index 015e506..48f69e8 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -47,6 +47,7 @@ app.notFound((c) => { }); v1.route("/teapot", teapot); +v1.route("/auth/redirect", await import("./auth/redirect").then(m => m.default)); app.route("/v1", v1); diff --git a/src/constants.ts b/src/constants.ts index 78c8670..73149c4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,34 +1,46 @@ import { readFileSync } from "fs"; -import { PrismaPg } from '@prisma/adapter-pg' -import { PrismaClient } from '../generated/prisma/client' - -const connectionString = `${process.env.DATABASE_URL}` -const adapter = new PrismaPg({ connectionString }) +import { PrismaPg } from "@prisma/adapter-pg"; +import { Prisma, PrismaClient } from "../generated/prisma/client"; +import * as msal from "@azure/msal-node"; +const connectionString = `${process.env.DATABASE_URL}`; +const adapter = new PrismaPg({ connectionString }); interface EnvKey { - PORT: number; -}; + PORT: number; +} // ENV CONSTANTS export const PORT = process.env.PORT; -export const prisma = new PrismaClient({ adapter }) +export const prisma = new PrismaClient({ adapter }); export const sessionDuration = 30 * 24 * 60 * 60000; export const accessTokenDuration = "10min"; export const refreshTokenDuration = "30d"; export const accessTokenPrivateKey = readFileSync( - `${import.meta.dir}/../.accessToken.key` + `${import.meta.dir}/../.accessToken.key`, ).toString(); export const refreshTokenPrivateKey = readFileSync( - `${import.meta.dir}/../.refreshToken.key` + `${import.meta.dir}/../.refreshToken.key`, ).toString(); export const permissionsPrivateKey = readFileSync( - `${import.meta.dir}/../.permissions.key` + `${import.meta.dir}/../.permissions.key`, ); export const apiKeyTokenPrivateKey = readFileSync( - `${import.meta.dir}/../.apiKeyToken.key` -); \ No newline at end of file + `${import.meta.dir}/../.apiKeyToken.key`, +); + +// Microsoft Auth Constants +const msalConfig: msal.Configuration = { + auth: { + clientId: process.env.MICROSOFT_CLIENT_ID!, + authority: `https://login.microsoftonline.com/${process.env.MICROSOFT_TENANT_ID!}`, + clientSecret: process.env.MICROSOFT_CLIENT_SECRET!, + }, +}; + +// MSAL Client Instance +export const msalClient = new msal.ConfidentialClientApplication(msalConfig); diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index d4d4276..93a9a12 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -10,8 +10,6 @@ import { events } from "../modules/globalEvents"; import { RoleController } from "./RoleController"; import { roles } from "../managers/roles"; - - export default class UserController { public id: string; public name: string | null; @@ -116,8 +114,8 @@ export default class UserController { await Promise.all( this._roles.map(async (v) => - collection.set(v.id, await roles.fetch(v.id)) - ) + collection.set(v.id, await roles.fetch(v.id)), + ), ); return collection; @@ -140,15 +138,6 @@ export default class UserController { sessions: { select: { id: true }, }, - apiKeys: { - select: { id: true }, - }, - projects: { - select: { id: true }, - }, - services: { - select: { id: true }, - }, }, }); @@ -158,7 +147,7 @@ export default class UserController { (v) => `resource.${v}.[${(resources![v] as { id: string }[]) .map((o) => o.id) - .join(",")}].user.${this.id}.implicit` + .join(",")}].user.${this.id}.implicit`, ); console.log(implicitPermissions); @@ -190,8 +179,8 @@ export default class UserController { roles: opts?.safeReturn ? undefined : this._roles.size > 0 - ? this._roles.map((v) => v.moniker) - : undefined, + ? this._roles.map((v) => v.moniker) + : undefined, login: opts?.safeReturn ? undefined : this.login, email: opts?.safeReturn ? undefined : this.email, image: this.image, diff --git a/src/managers/roles.ts b/src/managers/roles.ts index 8dd3c72..d1d609f 100644 --- a/src/managers/roles.ts +++ b/src/managers/roles.ts @@ -38,7 +38,7 @@ export const roles = { if (checkMoniker) throw new RoleError( "Moniker is already taken.", - "Another role with this moniker already exists in the databse." + "Another role with this moniker already exists in the databse.", ); const id = cuid(); @@ -76,7 +76,7 @@ export const roles = { * @param identifier - Options for fetching a role. * @returns {RoleController} - Role Controller */ - async fetch(identifier:string, opt?: { requestingUser?: UserController }) { + async fetch(identifier: string, opt?: { requestingUser?: UserController }) { const roleData = await prisma.role.findFirst({ where: { OR: [{ id: identifier }, { moniker: identifier }] }, include: { @@ -98,11 +98,11 @@ export const roles = { if ( opt?.requestingUser && !(await opt.requestingUser.hasPermission( - this._buildPermissionNode(roleData.id, "read") + this._buildPermissionNode(roleData.id, "read"), )) ) throw new InsufficientPermission( - "You do not have permission to access this role." + "You do not have permission to access this role.", ); const controller = new RoleController(roleData); @@ -123,20 +123,20 @@ export const roles = { include: { users: { include: { roles: true } } }, }); - roles. map((v:any) => collection.set(v.id, new RoleController(v))); + roles.map((v: any) => collection.set(v.id, new RoleController(v))); if (opt?.requestingUser) { const permittedRoles = await Promise.all( collection.map(async (v) => (await opt.requestingUser?.hasPermission( - this._buildPermissionNode(v.id, "read") + this._buildPermissionNode(v.id, "read"), )) ? v.id - : null - ) + : null, + ), ); collection = collection.filter((v) => - permittedRoles.filter((x) => x !== null).includes(v.id) + permittedRoles.filter((x) => x !== null).includes(v.id), ); } diff --git a/src/managers/sessions.ts b/src/managers/sessions.ts index 9817554..d4808cb 100644 --- a/src/managers/sessions.ts +++ b/src/managers/sessions.ts @@ -1,8 +1,6 @@ import { prisma, - refreshTokenDuration, sessionDuration, - accessTokenDuration, accessTokenPrivateKey, refreshTokenPrivateKey, } from "../constants"; diff --git a/src/managers/users.ts b/src/managers/users.ts index 53617ef..1928d9a 100644 --- a/src/managers/users.ts +++ b/src/managers/users.ts @@ -1,7 +1,12 @@ +import { ms } from "zod/locales"; import { User } from "../../generated/prisma/client"; import { prisma } from "../constants"; import { SessionTokensObject } from "../controllers/SessionController"; import UserController from "../controllers/UserController"; +import { fetchMicrosoftUser } from "../modules/fetchMicrosoftUser"; +import { events } from "../modules/globalEvents"; +import { sessions } from "./sessions"; +import * as msal from "@azure/msal-node"; export const users = { /** @@ -13,25 +18,22 @@ export const users = { * @summary It creates a user if one doesn't exist and will supply a session id * * @async - * @param ghCode - The code supplied in the callback url of a GitHub oAuth transaction + * @param authRequest - The code supplied in the callback url of the Microsoft oAuth transaction */ -/* async authenticate(ghCode: string): Promise { - const token = await ghApp.oauth.createToken({ code: ghCode }).catch((e) => { - throw new AuthenticationError("Invalid OAuth code..."); - }); - const userOK = await ghApp.oauth.getUserOctokit({ - token: token.authentication.token, - }); - const ghUser = await userOK.request("GET /user"); + async authenticate( + authRequest: msal.AuthenticationResult, + ): Promise { + let id = authRequest.uniqueId as string; + let user = - (await this.fetchUser({ userId: ghUser.data.id })) ?? - (await this.createUser(token.authentication.token)); + (await this.fetchUser({ userId: id })) ?? + (await this.createUser(authRequest.accessToken)); const tokens = await sessions.create({ user }); events.emit("user:authenticated", { user, tokens }); return tokens; - }, */ + }, /** * Check to see if the user exists @@ -59,8 +61,8 @@ export const users = { id: string; email: string; login: string; - userId: number; - }> + userId: string; + }>, ) { if (Object.keys(identifier).length == 0) return null; const userData = await prisma.user.findFirst({ @@ -79,28 +81,21 @@ export const users = { /** * Create a new user * - * This method will poll GitHub and get all the information on the user to then create the + * This method will poll Microsoft and get all the information on the user to then create the * record in our database. On top of that it also pushes it into the user cache. * - * @param token - The Github token provided by the auth method + * @param token - The Microsoft token provided by the auth method * @returns {Promise} The new user controller for the user */ async createUser(token: string): Promise { - const ghUser = await ( - await ghApp.oauth.getUserOctokit({ token }) - ).request("GET /user"); - - const emails = await ( - await ghApp.oauth.getUserOctokit({ token }) - ).request("GET /user/emails"); + const msData = await fetchMicrosoftUser(token); const newUser = await prisma.user.create({ data: { - userId: ghUser.data.id, - email: emails.data[0].email, - image: ghUser.data.avatar_url, - name: ghUser.data.name, - login: ghUser.data.login, + userId: msData.id, + email: msData.mail, + name: `${msData.givenName} ${msData.surname}`, + login: msData.userPrincipalName, token, }, include: { roles: true }, diff --git a/src/modules/fetchMicrosoftUser.ts b/src/modules/fetchMicrosoftUser.ts new file mode 100644 index 0000000..24181db --- /dev/null +++ b/src/modules/fetchMicrosoftUser.ts @@ -0,0 +1,33 @@ +import { MicrosoftGraphUser } from "../types/MSAuthTypes"; + +/** + * Fetch Microsoft User + * + * This function fetches user data from Microsoft Graph API using the provided access token. + * It makes a GET request to the `/me` endpoint and returns the user data in JSON format. + * + * @param accessToken - This is the access token provided by Microsoft. + * @returns - Raw API Data from Microsoft. + */ +export const fetchMicrosoftUser = async ( + accessToken: string, +): Promise => { + const graphEndpoint = "https://graph.microsoft.com/v1.0/me"; + + const response = await fetch(graphEndpoint, { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + if (!response.ok) { + throw new Error( + `Graph request failed: ${response.status} ${response.statusText}`, + ); + } + + const data = (await response.json()) as MicrosoftGraphUser; + + return data; +}; diff --git a/src/types/MSAuthTypes.ts b/src/types/MSAuthTypes.ts new file mode 100644 index 0000000..4d8671d --- /dev/null +++ b/src/types/MSAuthTypes.ts @@ -0,0 +1,14 @@ +export interface MicrosoftGraphUser { + "@odata.context": string; + businessPhones: string[]; + displayName: string; + givenName: string; + jobTitle: string | null; + mail: string; + mobilePhone: string | null; + officeLocation: string | null; + preferredLanguage: string | null; + surname: string; + userPrincipalName: string; + id: string; +} diff --git a/tsconfig.json b/tsconfig.json index d23842c..eff676a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,10 @@ // Some stricter flags (disabled by default) "noUnusedLocals": false, "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false + "noPropertyAccessFromIndexSignature": false, + + "paths": { + "@prisma/client": ["./generated/prisma"] + } } }