From 14c9b0210c0afe82c828d0f8b48bb8b2cce0b637 Mon Sep 17 00:00:00 2001 From: Akhil Reddy Date: Fri, 3 Jan 2025 12:32:43 +0530 Subject: [PATCH] Add session management --- package.json | 3 + pnpm-lock.yaml | 126 ++++++++++++++++++++ src/auth.ts | 51 ++++++-- src/auth/auth.route.ts | 64 ++++++++++ src/auth/auth.schema.ts | 20 ++++ src/auth/auth.service.ts | 24 ++++ src/oauth.ts | 24 ++++ src/organization/organization.controller.ts | 2 +- src/server.ts | 10 ++ src/user/user.controller.ts | 3 +- src/user/user.schema.ts | 16 +++ src/user/user.service.ts | 14 ++- 12 files changed, 341 insertions(+), 16 deletions(-) create mode 100644 src/auth/auth.route.ts create mode 100644 src/auth/auth.schema.ts create mode 100644 src/auth/auth.service.ts create mode 100644 src/oauth.ts diff --git a/package.json b/package.json index 32f3b24..b2a3503 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,10 @@ "dependencies": { "@aws-sdk/client-s3": "^3.717.0", "@aws-sdk/s3-request-presigner": "^3.717.0", + "@fastify/cookie": "^11.0.1", + "@fastify/cors": "^10.0.1", "@fastify/multipart": "^9.0.1", + "@fastify/oauth2": "^8.1.0", "@paralleldrive/cuid2": "^2.2.2", "bcrypt": "^5.1.1", "fastify": "^5.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5686558..5fd889b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,18 @@ importers: '@aws-sdk/s3-request-presigner': specifier: ^3.717.0 version: 3.717.0 + '@fastify/cookie': + specifier: ^11.0.1 + version: 11.0.1 + '@fastify/cors': + specifier: ^10.0.1 + version: 10.0.1 '@fastify/multipart': specifier: ^9.0.1 version: 9.0.1 + '@fastify/oauth2': + specifier: ^8.1.0 + version: 8.1.0 '@paralleldrive/cuid2': specifier: ^2.2.2 version: 2.2.2 @@ -232,6 +241,12 @@ packages: '@fastify/busboy@3.1.1': resolution: {integrity: sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==} + '@fastify/cookie@11.0.1': + resolution: {integrity: sha512-n1Ooz4bgQ5LcOlJQboWPfsMNxIrGV0SgU85UkctdpTlCQE0mtA3rlspOPUdqk9ubiiZn053ucnia4DjTquI4/g==} + + '@fastify/cors@10.0.1': + resolution: {integrity: sha512-O8JIf6448uQbOgzSkCqhClw6gFTAqrdfeA6R3fc/3gwTJGUp7gl8/3tbNB+6INuu4RmgVOq99BmvdGbtu5pgOA==} + '@fastify/deepmerge@2.0.1': resolution: {integrity: sha512-hx+wJQr9Ph1hY/dyzY0SxqjumMyqZDlIF6oe71dpRKDHUg7dFQfjG94qqwQ274XRjmUrwKiYadex8XplNHx3CA==} @@ -247,6 +262,9 @@ packages: '@fastify/multipart@9.0.1': resolution: {integrity: sha512-vt2gOCw/O4EwpN4KlLVJxth4iQlDf7T5ggw2Db2C+UbO2WJBG7y0jEBvu/HT6JIW/lBYaqrrUy9MmTpCKgXEpw==} + '@fastify/oauth2@8.1.0': + resolution: {integrity: sha512-SMcvStTvhF+UcyH+7uLWvKrxM4g5evZafjtQWRAg02C/dlsOrqLK0s9/1aszhWY6PBklJ6jduFPpl+sB4l2DlQ==} + '@fastify/send@2.1.0': resolution: {integrity: sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==} @@ -259,6 +277,24 @@ packages: '@fastify/swagger@8.15.0': resolution: {integrity: sha512-zy+HEEKFqPMS2sFUsQU5X0MHplhKJvWeohBwTCkBAJA/GDYGLGUWQaETEhptiqxK7Hs0fQB9B4MDb3pbwIiCwA==} + '@hapi/boom@10.0.1': + resolution: {integrity: sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==} + + '@hapi/bourne@3.0.0': + resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} + + '@hapi/hoek@11.0.7': + resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} + + '@hapi/hoek@9.3.0': + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + '@hapi/topo@5.1.0': + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + + '@hapi/wreck@18.1.0': + resolution: {integrity: sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w==} + '@lukeed/ms@2.0.2': resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} @@ -277,6 +313,15 @@ packages: '@paralleldrive/cuid2@2.2.2': resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + + '@sideway/formula@3.0.1': + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + '@sideway/pinpoint@2.0.0': + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + '@smithy/abort-controller@3.1.9': resolution: {integrity: sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw==} engines: {node: '>=16.0.0'} @@ -728,6 +773,9 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -788,6 +836,9 @@ packages: engines: {node: '>=10'} hasBin: true + mnemonist@0.39.8: + resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==} + mongodb-connection-string-url@3.0.1: resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==} @@ -861,6 +912,9 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + obliterator@2.0.4: + resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -980,6 +1034,9 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + simple-oauth2@5.1.0: + resolution: {integrity: sha512-gWDa38Ccm4MwlG5U7AlcJxPv3lvr80dU7ARJWrGdgvOKyzSj1gr3GBPN1rABTedAYvC/LsGYoFuFxwDBPtGEbw==} + snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} @@ -1637,6 +1694,16 @@ snapshots: '@fastify/busboy@3.1.1': {} + '@fastify/cookie@11.0.1': + dependencies: + cookie: 1.0.2 + fastify-plugin: 5.0.1 + + '@fastify/cors@10.0.1': + dependencies: + fastify-plugin: 5.0.1 + mnemonist: 0.39.8 + '@fastify/deepmerge@2.0.1': {} '@fastify/error@4.0.0': {} @@ -1657,6 +1724,14 @@ snapshots: fastify-plugin: 5.0.1 secure-json-parse: 3.0.1 + '@fastify/oauth2@8.1.0': + dependencies: + '@fastify/cookie': 11.0.1 + fastify-plugin: 5.0.1 + simple-oauth2: 5.1.0 + transitivePeerDependencies: + - supports-color + '@fastify/send@2.1.0': dependencies: '@lukeed/ms': 2.0.2 @@ -1692,6 +1767,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@hapi/boom@10.0.1': + dependencies: + '@hapi/hoek': 11.0.7 + + '@hapi/bourne@3.0.0': {} + + '@hapi/hoek@11.0.7': {} + + '@hapi/hoek@9.3.0': {} + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + + '@hapi/wreck@18.1.0': + dependencies: + '@hapi/boom': 10.0.1 + '@hapi/bourne': 3.0.0 + '@hapi/hoek': 11.0.7 + '@lukeed/ms@2.0.2': {} '@mapbox/node-pre-gyp@1.0.11': @@ -1719,6 +1814,14 @@ snapshots: dependencies: '@noble/hashes': 1.6.1 + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + '@smithy/abort-controller@3.1.9': dependencies: '@smithy/types': 3.7.2 @@ -2345,6 +2448,14 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + joi@17.13.3: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -2404,6 +2515,10 @@ snapshots: mkdirp@1.0.4: {} + mnemonist@0.39.8: + dependencies: + obliterator: 2.0.4 + mongodb-connection-string-url@3.0.1: dependencies: '@types/whatwg-url': 11.0.5 @@ -2468,6 +2583,8 @@ snapshots: object-assign@4.1.1: {} + obliterator@2.0.4: {} + on-exit-leak-free@2.1.2: {} once@1.4.0: @@ -2578,6 +2695,15 @@ snapshots: signal-exit@3.0.7: {} + simple-oauth2@5.1.0: + dependencies: + '@hapi/hoek': 11.0.7 + '@hapi/wreck': 18.1.0 + debug: 4.4.0 + joi: 17.13.3 + transitivePeerDependencies: + - supports-color + snake-case@3.0.4: dependencies: dot-case: 3.0.4 diff --git a/src/auth.ts b/src/auth.ts index 6a50580..5fc588e 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -2,8 +2,11 @@ import bcrypt from "bcrypt"; import { FastifyReply, FastifyRequest } from "fastify"; import { getToken } from "./tokens/token.service"; import { Claim } from "./utils/claims"; +import { OAuth2Namespace } from "@fastify/oauth2"; +import { getSession } from "./auth/auth.service"; export type AuthenticatedUser = { + sid?: string; userId?: string; tenantId: string; claims: Array; @@ -16,6 +19,7 @@ declare module "fastify" { export interface FastifyInstance { authorize: (req: FastifyRequest, res: FastifyReply) => Promise; + microsoftOauth: OAuth2Namespace; } export interface FastifyContextConfig { @@ -26,19 +30,46 @@ declare module "fastify" { export async function authHandler(req: FastifyRequest, res: FastifyReply) { if (!req.headers.authorization) return res.code(401).send(); - const [tokenId, token] = req.headers.authorization.split(" ")[1].split("."); - if (!tokenId || !token) return res.code(401).send({ error: "invalid token" }); + const authHeader = req.headers.authorization.split(" ")[1]; + if (!authHeader || authHeader == "") + return res.code(401).send({ error: "invalid_token" }); - const tokenInDb = await getToken(tokenId); - if (tokenInDb === null) return res.code(401).send({ error: "invalid token" }); + if (authHeader.includes(".")) { + const [tokenId, token] = authHeader.split("."); + if (!tokenId || !token) + return res.code(401).send({ error: "invalid_token" }); - const valid = await bcrypt.compare(token, tokenInDb.hash); - if (!valid) return res.code(401).send({ error: "invalid token" }); + const tokenInDb = await getToken(tokenId); + if (tokenInDb === null) + return res.code(401).send({ error: "invalid_token" }); - req.user = { - tenantId: tokenInDb.tenantId, - claims: tokenInDb.claims as Array, - }; + const valid = await bcrypt.compare(token, tokenInDb.hash); + if (!valid) return res.code(401).send({ error: "invalid_token" }); + + req.user = { + tenantId: tokenInDb.tenantId, + claims: tokenInDb.claims as Array, + }; + } else { + const sessionInDb = await getSession(authHeader); + if (sessionInDb === null) + return res.code(401).send({ error: "invalid_token" }); + + if (new Date() > new Date(sessionInDb.expiresAt)) { + await sessionInDb.deleteOne(); + return res.code(401).send({ error: "session_expired" }); + } + + req.user = { + sid: authHeader, + //@ts-ignore + userId: sessionInDb.user.id, + //@ts-ignore + tenantId: sessionInDb.user.tenantId, + //@ts-ignore + claims: sessionInDb.user.claims, + }; + } } export function hasValidClaims( diff --git a/src/auth/auth.route.ts b/src/auth/auth.route.ts new file mode 100644 index 0000000..c542322 --- /dev/null +++ b/src/auth/auth.route.ts @@ -0,0 +1,64 @@ +import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; +import { getUserByEmail, updateUser } from "../user/user.service"; +import { createSession, getSession } from "./auth.service"; + +export async function authRoutes(fastify: FastifyInstance) { + fastify.get( + "/microsoft/token", + {}, + async (req: FastifyRequest, res: FastifyReply) => { + try { + const { token } = + await fastify.microsoftOauth.getAccessTokenFromAuthorizationCodeFlow( + req + ); + + const user = (await fastify.microsoftOauth.userinfo(token)) as { + givenname: string; + familyname: string; + email: string; + picture: string; + }; + + const userInDb = await getUserByEmail(user.email); + if (userInDb == null) + return res.code(401).send({ error: "not_allowed" }); + + await updateUser(userInDb.pid, { + firstName: user.givenname, + lastName: user.familyname, + email: user.email, + avatar: user.picture, + }); + + const session = await createSession( + userInDb.id, + req.ip, + req.headers["user-agent"] + ); + + return res.code(201).send({ session_token: session.sid }); + } catch (err) { + //@ts-ignore + if (err.data) { + //@ts-ignore + fastify.log.warn(err.data.payload); + return res.code(400).send(); + } else { + return err; + } + } + } + ); + + fastify.delete("/logout", {}, async (req, res) => { + if (!req.headers.authorization) return res.code(200).send(); + + const auth = req.headers.authorization.split(" ")[1]; + const sessionInDb = await getSession(auth); + if (sessionInDb === null) return res.code(200).send(); + + await sessionInDb.deleteOne(); + return res.code(200).send(); + }); +} diff --git a/src/auth/auth.schema.ts b/src/auth/auth.schema.ts new file mode 100644 index 0000000..37195aa --- /dev/null +++ b/src/auth/auth.schema.ts @@ -0,0 +1,20 @@ +import mongoose from "mongoose"; + +export const sessionModel = mongoose.model( + "session", + new mongoose.Schema({ + sid: { + type: String, + unique: true, + required: true, + }, + user: { type: mongoose.Types.ObjectId, ref: "user" }, + ip: String, + userAgent: String, + createdAt: Date, + expiresAt: { + type: Date, + required: true, + }, + }) +); diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000..c712e70 --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,24 @@ +import { generateToken } from "../utils/id"; +import { sessionModel } from "./auth.schema"; + +export async function createSession(userId: string, ip?: string, ua?: string) { + const allUserSessions = await sessionModel.find({ user: userId }); + for (const session of allUserSessions) { + await session.deleteOne(); + } + + const newSession = await sessionModel.create({ + sid: await generateToken(), + user: userId, + ip: ip, + userAgent: ua, + createdAt: new Date(), + expiresAt: new Date(Date.now() + 3600 * 24 * 30 * 1000), + }); + + return newSession; +} + +export async function getSession(sessionId: string) { + return await sessionModel.findOne({ sid: sessionId }).populate("user"); +} diff --git a/src/oauth.ts b/src/oauth.ts new file mode 100644 index 0000000..45cf213 --- /dev/null +++ b/src/oauth.ts @@ -0,0 +1,24 @@ +import oauthPlugin, { FastifyOAuth2Options } from "@fastify/oauth2"; +import { FastifyInstance } from "fastify"; + +export async function oauth(fastify: FastifyInstance) { + fastify.register(oauthPlugin, { + name: "microsoftOauth", + scope: ["openid", "email", "profile"], + credentials: { + client: { + id: process.env.MICROSOFT_CLIENT_ID, + secret: process.env.MICROSOFT_CLIENT_SECRET, + }, + }, + startRedirectPath: "/auth/microsoft", + callbackUri: process.env.MICROSOFT_REDIRECT_URI, + discovery: { + issuer: + "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration", + }, + cookie: { + sameSite: "none", + }, + } as FastifyOAuth2Options); +} diff --git a/src/organization/organization.controller.ts b/src/organization/organization.controller.ts index 5834203..b3d2828 100644 --- a/src/organization/organization.controller.ts +++ b/src/organization/organization.controller.ts @@ -38,7 +38,7 @@ export async function getOrgHandler(req: FastifyRequest, res: FastifyReply) { export async function listOrgsHandler(req: FastifyRequest, res: FastifyReply) { const queryParams = req.query as PageQueryParams; - + console.log(req.user); try { const authUser = req.user; const orgList = await listOrgs(queryParams, authUser.tenantId); diff --git a/src/server.ts b/src/server.ts index 368fe0b..c59f09a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,4 +1,5 @@ import fastify from "fastify"; +import cors from "@fastify/cors"; import multipart from "@fastify/multipart"; import routes from "./routes"; @@ -9,6 +10,8 @@ import { errorHandler } from "./utils/errors"; import { authorize } from "./auth"; import { permitSchemas } from "./permit/permit.schema"; import { fileSchemas } from "./file/file.schema"; +import { oauth } from "./oauth"; +import { authRoutes } from "./auth/auth.route"; const app = fastify({ logger: true }); @@ -16,9 +19,16 @@ app.get("/health", (req, res) => { return { status: "OK" }; }); +oauth(app); + app.decorate("authorize", authorize); app.setErrorHandler(errorHandler); +app.register(cors, { + origin: [process.env.UI_DOMAIN || ""], + credentials: true, +}); app.register(multipart, { limits: { fileSize: 50000000 } }); +app.register(authRoutes, { prefix: "/auth" }); app.register(routes, { prefix: "/api/v1" }); for (const schema of [ diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 9be91d5..0eef2dc 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -21,8 +21,7 @@ export async function getUserHandler(req: FastifyRequest, res: FastifyReply) { const { userId } = req.params as { userId: string }; try { - const authUser = req.user; - const user = await getUser(userId, authUser.tenantId); + const user = await getUser(userId); if (user == null) return res.code(404).send({ error: "resource not found" }); diff --git a/src/user/user.schema.ts b/src/user/user.schema.ts index 7c4ba90..93f9372 100644 --- a/src/user/user.schema.ts +++ b/src/user/user.schema.ts @@ -24,6 +24,7 @@ export const userModel = mongoose.model( }, avatar: String, status: String, + claims: [String], createdAt: Date, createdBy: mongoose.Types.ObjectId, lastLogin: Date, @@ -40,6 +41,7 @@ const userCore = { }) .email(), avatar: z.string().url().optional(), + claims: z.array(z.string()).optional(), }; const createUserInput = z.object({ @@ -51,7 +53,21 @@ const createUserResponse = z.object({ ...userCore, }); +const updateUserInput = z.object({ + firstName: z.string().max(30).optional(), + lastName: z.string().max(30).optional(), + email: z + .string({ + required_error: "Email is required", + invalid_type_error: "Email must be a valid string", + }) + .email() + .optional(), + avatar: z.string().url().optional(), +}); + export type CreateUserInput = z.infer; +export type UpdateUserInput = z.infer; export const { schemas: userSchemas, $ref: $user } = buildJsonSchemas( { diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 93363b3..1f45559 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,5 +1,5 @@ import { generateId } from "../utils/id"; -import { CreateUserInput, userModel } from "./user.schema"; +import { CreateUserInput, UpdateUserInput, userModel } from "./user.schema"; export async function createUser(input: CreateUserInput, tenantId: string) { const user = await userModel.create({ @@ -13,9 +13,17 @@ export async function createUser(input: CreateUserInput, tenantId: string) { return user; } -export async function getUser(userId: string, tenantId: string) { +export async function getUser(userId: string) { const user = await userModel.findOne({ - $and: [{ tenantId: tenantId }, { pid: userId }], + $and: [{ pid: userId }], }); return user; } + +export async function getUserByEmail(email: string) { + return await userModel.findOne({ email: email }); +} + +export async function updateUser(userId: string, input: UpdateUserInput) { + return await userModel.findOneAndUpdate({ pid: userId }, input); +}