Add session management
This commit is contained in:
@@ -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",
|
||||
|
||||
126
pnpm-lock.yaml
generated
126
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
51
src/auth.ts
51
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<Claim>;
|
||||
@@ -16,6 +19,7 @@ declare module "fastify" {
|
||||
|
||||
export interface FastifyInstance {
|
||||
authorize: (req: FastifyRequest, res: FastifyReply) => Promise<unknown>;
|
||||
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<Claim>,
|
||||
};
|
||||
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<Claim>,
|
||||
};
|
||||
} 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(
|
||||
|
||||
64
src/auth/auth.route.ts
Normal file
64
src/auth/auth.route.ts
Normal file
@@ -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();
|
||||
});
|
||||
}
|
||||
20
src/auth/auth.schema.ts
Normal file
20
src/auth/auth.schema.ts
Normal file
@@ -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,
|
||||
},
|
||||
})
|
||||
);
|
||||
24
src/auth/auth.service.ts
Normal file
24
src/auth/auth.service.ts
Normal file
@@ -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");
|
||||
}
|
||||
24
src/oauth.ts
Normal file
24
src/oauth.ts
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -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" });
|
||||
|
||||
|
||||
@@ -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<typeof createUserInput>;
|
||||
export type UpdateUserInput = z.infer<typeof updateUserInput>;
|
||||
|
||||
export const { schemas: userSchemas, $ref: $user } = buildJsonSchemas(
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user