add user session
This commit is contained in:
@@ -1 +0,0 @@
|
||||
PORT=5000
|
||||
2
server/.env.example
Normal file
2
server/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
PORT=5000
|
||||
SECRET=aE8efkEP8+V/ibEQl8IKbw==
|
||||
1
server/.gitignore
vendored
1
server/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
coverage
|
||||
.env
|
||||
@@ -14,7 +14,9 @@
|
||||
"class-validator": "^0.13.2",
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "^4.18.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"pg": "^8.7.3",
|
||||
"redis": "^4.2.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"typeorm": "^0.3.7"
|
||||
},
|
||||
@@ -22,6 +24,7 @@
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "^28.1.4",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"@types/node": "^18.0.3",
|
||||
"concurrently": "^7.2.2",
|
||||
"jest": "^28.1.2",
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { RequestHandler, Router } from 'express'
|
||||
import { Router } from 'express';
|
||||
import { UserDto } from '../dto/user.dto';
|
||||
import { AuthService } from '../service/auth.service';
|
||||
import { UserService } from '../service/user.service';
|
||||
import { createPath as buildPath } from '../utils/createPath';
|
||||
|
||||
const router = Router();
|
||||
|
||||
const basePath = '/users'
|
||||
const BASE_PATH = '/users'
|
||||
|
||||
export const post: RequestHandler = (req, res) => {
|
||||
export const createPath = BASE_PATH
|
||||
|
||||
router.post(createPath, (req, res) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
UserService.create({
|
||||
@@ -24,7 +28,46 @@ export const post: RequestHandler = (req, res) => {
|
||||
error: err.message
|
||||
});
|
||||
})
|
||||
}
|
||||
router.post(basePath, post)
|
||||
})
|
||||
|
||||
export const signInPath = buildPath(BASE_PATH, 'sign_in')
|
||||
router.post(signInPath, async (req, res) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await UserService.findByEmail(email)
|
||||
|
||||
const isPasswordValid = await AuthService.isUserPasswordValid(user, password)
|
||||
|
||||
if (isPasswordValid) {
|
||||
const token = await AuthService.createSession(user)
|
||||
|
||||
res.json({
|
||||
auth: true,
|
||||
token
|
||||
})
|
||||
} else {
|
||||
res.status(500).json({
|
||||
auth: false,
|
||||
token: null,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const signOutPath = buildPath(BASE_PATH, 'sign_out')
|
||||
router.delete(signOutPath, async (req, res) => {
|
||||
const token = req.headers['x-access-token']
|
||||
|
||||
if (typeof token === 'string') {
|
||||
await AuthService.destoySession(token)
|
||||
|
||||
res.status(204).json({
|
||||
success: true
|
||||
})
|
||||
} else {
|
||||
res.status(422).json({
|
||||
success: false
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const UserRoutes = router;
|
||||
3
server/src/dto/session.dto.ts
Normal file
3
server/src/dto/session.dto.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
type SessionDto = {
|
||||
userEmail: string;
|
||||
}
|
||||
5
server/src/express.d.ts
vendored
Normal file
5
server/src/express.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare namespace Express {
|
||||
export interface Request {
|
||||
userId?: number
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,33 @@ import * as dotenv from 'dotenv';
|
||||
import * as express from 'express';
|
||||
import { AppDataSource } from "./infra/dataSource";
|
||||
import { UserRoutes } from "./controller";
|
||||
import { RedisConnection } from "./infra/redis";
|
||||
import { sessionMiddleware } from "./middleware/session.middleware";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.use(sessionMiddleware)
|
||||
app.use(UserRoutes)
|
||||
|
||||
AppDataSource.initialize().then(() => {
|
||||
|
||||
const startApp = async () => {
|
||||
console.log('[redis]: connecting')
|
||||
await RedisConnection.connect()
|
||||
console.log('[redis]: connected')
|
||||
|
||||
console.log('[database]: connecting')
|
||||
await AppDataSource.initialize()
|
||||
console.log('[database]: connected')
|
||||
|
||||
const port = process.env.PORT;
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running at ${port} 🚀`);
|
||||
console.log(`[server] is running at ${port} 🚀`);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
startApp()
|
||||
5
server/src/infra/redis.ts
Normal file
5
server/src/infra/redis.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createClient } from 'redis';
|
||||
|
||||
export const RedisConnection = createClient();
|
||||
|
||||
RedisConnection.on('error', (err) => console.log('Redis Client Error', err));
|
||||
8
server/src/middleware/__test__/unprotectedRoutes.spec.ts
Normal file
8
server/src/middleware/__test__/unprotectedRoutes.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { signInPath } from '../../controller/users.controller';
|
||||
import { UNPROTECTED_ROUTES } from '../session.middleware'
|
||||
|
||||
describe('Unprotected Routes', () => {
|
||||
it('check content', () => {
|
||||
expect(UNPROTECTED_ROUTES.sort()).toEqual([signInPath].sort());
|
||||
})
|
||||
})
|
||||
28
server/src/middleware/session.middleware.ts
Normal file
28
server/src/middleware/session.middleware.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Handler, Request, Response } from 'express';
|
||||
import { verify } from 'jsonwebtoken';
|
||||
import { signInPath } from '../controller/users.controller';
|
||||
import { AuthService } from '../service/auth.service';
|
||||
|
||||
export const UNPROTECTED_ROUTES = [signInPath];
|
||||
|
||||
export const sessionMiddleware: Handler = (req: Request, res: Response, next) => {
|
||||
const token = req.headers['x-access-token'];
|
||||
|
||||
if (UNPROTECTED_ROUTES.includes(req.url)) {
|
||||
next();
|
||||
} else if (typeof token === 'string') {
|
||||
AuthService.isSessionValid(token).then(valid => {
|
||||
verify(token, process.env.SECRET, function (err, decoded) {
|
||||
if (err || !valid) return res.status(500).json({ auth: false, message: 'Failed to authenticate token.' });
|
||||
|
||||
if (!(typeof decoded === 'string')) {
|
||||
req.userId = +decoded.sub;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
})
|
||||
} else {
|
||||
return res.status(401).json({ auth: false, message: 'No token provided.' })
|
||||
}
|
||||
}
|
||||
25
server/src/repository/session.repository.ts
Normal file
25
server/src/repository/session.repository.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { RedisConnection } from "../infra/redis"
|
||||
|
||||
const sessionExists = async (jwt: string): Promise<boolean> => {
|
||||
const result = await RedisConnection.get(jwt)
|
||||
|
||||
return result == "valid"
|
||||
}
|
||||
|
||||
const saveSession = async (jwt: string): Promise<boolean> => {
|
||||
const result = await RedisConnection.set(jwt, 'valid')
|
||||
|
||||
return result == "valid"
|
||||
}
|
||||
|
||||
const deleteSession = async (jwt: string): Promise<boolean> => {
|
||||
const result = await RedisConnection.del(jwt)
|
||||
|
||||
return !!result
|
||||
}
|
||||
|
||||
export const sessionRepository = {
|
||||
sessionExists,
|
||||
saveSession,
|
||||
deleteSession,
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { User } from "../entity/user.entity";
|
||||
import * as bcrypt from 'bcrypt'
|
||||
import { JwtPayload, sign as JwtSign } from "jsonwebtoken";
|
||||
import { sessionRepository } from "../repository/session.repository";
|
||||
|
||||
export const SALT_ROUNDS = 10
|
||||
|
||||
@@ -29,7 +31,32 @@ const isUserPasswordValid = (user: User, password: string) => {
|
||||
})
|
||||
}
|
||||
|
||||
const isSessionValid = (token: string): Promise<boolean> => {
|
||||
return sessionRepository.sessionExists(token)
|
||||
}
|
||||
|
||||
const createSession = async (user: User): Promise<string> => {
|
||||
const payload: JwtPayload = {
|
||||
sub: user.id.toString(),
|
||||
}
|
||||
|
||||
const token = JwtSign(payload, process.env.SECRET);
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
sessionRepository.saveSession(token)
|
||||
.then(() => resolve(token))
|
||||
.catch(reject)
|
||||
})
|
||||
}
|
||||
|
||||
const destoySession = (token: string): Promise<boolean> => {
|
||||
return sessionRepository.deleteSession(token)
|
||||
}
|
||||
|
||||
export const AuthService = {
|
||||
updateUserPassword,
|
||||
isUserPasswordValid
|
||||
isUserPasswordValid,
|
||||
isSessionValid,
|
||||
createSession,
|
||||
destoySession
|
||||
}
|
||||
@@ -31,7 +31,12 @@ async function findByEmail(email: string): Promise<User | undefined> {
|
||||
return userRepository.findOneBy({ email })
|
||||
}
|
||||
|
||||
async function findUserById(id: number): Promise<User | undefined> {
|
||||
return userRepository.findOneBy({ id })
|
||||
}
|
||||
|
||||
export const UserService = {
|
||||
create,
|
||||
findByEmail
|
||||
findByEmail,
|
||||
findUserById
|
||||
}
|
||||
3
server/src/utils/createPath.ts
Normal file
3
server/src/utils/createPath.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const createPath = (...args: string[]): string => {
|
||||
return args.join('/')
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es5", "es6"],
|
||||
"target": "es5",
|
||||
"lib": ["es6"],
|
||||
"target": "ES2022",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "./src",
|
||||
|
||||
147
server/yarn.lock
147
server/yarn.lock
@@ -563,6 +563,40 @@
|
||||
semver "^7.3.5"
|
||||
tar "^6.1.11"
|
||||
|
||||
"@redis/bloom@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-1.0.2.tgz#42b82ec399a92db05e29fffcdfd9235a5fc15cdf"
|
||||
integrity sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==
|
||||
|
||||
"@redis/client@1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@redis/client/-/client-1.2.0.tgz#be2ef974881e57276123cb76d08756c03eed946f"
|
||||
integrity sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==
|
||||
dependencies:
|
||||
cluster-key-slot "1.1.0"
|
||||
generic-pool "3.8.2"
|
||||
yallist "4.0.0"
|
||||
|
||||
"@redis/graph@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@redis/graph/-/graph-1.0.1.tgz#eabc58ba99cd70d0c907169c02b55497e4ec8a99"
|
||||
integrity sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==
|
||||
|
||||
"@redis/json@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@redis/json/-/json-1.0.3.tgz#a13fde1d22ebff0ae2805cd8e1e70522b08ea866"
|
||||
integrity sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==
|
||||
|
||||
"@redis/search@1.0.6":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@redis/search/-/search-1.0.6.tgz#53d7451c2783f011ebc48ec4c2891264e0b22f10"
|
||||
integrity sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==
|
||||
|
||||
"@redis/time-series@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@redis/time-series/-/time-series-1.0.3.tgz#4cfca8e564228c0bddcdf4418cba60c20b224ac4"
|
||||
integrity sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==
|
||||
|
||||
"@sinclair/typebox@^0.23.3":
|
||||
version "0.23.5"
|
||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.23.5.tgz#93f7b9f4e3285a7a9ade7557d9a8d36809cbc47d"
|
||||
@@ -715,6 +749,13 @@
|
||||
jest-matcher-utils "^28.0.0"
|
||||
pretty-format "^28.0.0"
|
||||
|
||||
"@types/jsonwebtoken@^8.5.8":
|
||||
version "8.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz#01b39711eb844777b7af1d1f2b4cf22fda1c0c44"
|
||||
integrity sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/mime@^1":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
||||
@@ -1019,6 +1060,11 @@ bser@2.1.1:
|
||||
dependencies:
|
||||
node-int64 "^0.4.0"
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
@@ -1151,6 +1197,11 @@ cliui@^7.0.2:
|
||||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
cluster-key-slot@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
||||
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
@@ -1334,6 +1385,13 @@ dotenv@^16.0.0, dotenv@^16.0.1:
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d"
|
||||
integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
@@ -1551,6 +1609,11 @@ gauge@^3.0.0:
|
||||
strip-ansi "^6.0.1"
|
||||
wide-align "^1.1.2"
|
||||
|
||||
generic-pool@3.8.2:
|
||||
version "3.8.2"
|
||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.8.2.tgz#aab4f280adb522fdfbdc5e5b64d718d3683f04e9"
|
||||
integrity sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==
|
||||
|
||||
gensync@^1.0.0-beta.2:
|
||||
version "1.0.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||
@@ -2209,6 +2272,39 @@ json5@^2.2.1:
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
|
||||
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
|
||||
|
||||
jsonwebtoken@^8.5.1:
|
||||
version "8.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
|
||||
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
|
||||
dependencies:
|
||||
jws "^3.2.2"
|
||||
lodash.includes "^4.3.0"
|
||||
lodash.isboolean "^3.0.3"
|
||||
lodash.isinteger "^4.0.4"
|
||||
lodash.isnumber "^3.0.3"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.isstring "^4.0.1"
|
||||
lodash.once "^4.0.0"
|
||||
ms "^2.1.1"
|
||||
semver "^5.6.0"
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||
dependencies:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
kleur@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
@@ -2236,11 +2332,46 @@ locate-path@^5.0.0:
|
||||
dependencies:
|
||||
p-locate "^4.1.0"
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
|
||||
|
||||
lodash.isboolean@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
|
||||
|
||||
lodash.isinteger@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
|
||||
|
||||
lodash.isnumber@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||
|
||||
lodash.memoize@4.x:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
|
||||
|
||||
lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
@@ -2738,6 +2869,18 @@ readdirp@~3.6.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
redis@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/redis/-/redis-4.2.0.tgz#1278a265b8aa1e096a585d103bdead027cd04e43"
|
||||
integrity sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==
|
||||
dependencies:
|
||||
"@redis/bloom" "1.0.2"
|
||||
"@redis/client" "1.2.0"
|
||||
"@redis/graph" "1.0.1"
|
||||
"@redis/json" "1.0.3"
|
||||
"@redis/search" "1.0.6"
|
||||
"@redis/time-series" "1.0.3"
|
||||
|
||||
reflect-metadata@^0.1.13:
|
||||
version "0.1.13"
|
||||
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
|
||||
@@ -2815,7 +2958,7 @@ semver@7.x, semver@^7.3.5:
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^5.7.1:
|
||||
semver@^5.6.0, semver@^5.7.1:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
@@ -3352,7 +3495,7 @@ y18n@^5.0.5:
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
||||
|
||||
yallist@^4.0.0:
|
||||
yallist@4.0.0, yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
Reference in New Issue
Block a user