added new changes
This commit is contained in:
parent
24bb300612
commit
135696eb2a
13
.env
13
.env
@ -1,8 +1,8 @@
|
||||
ENV=dev
|
||||
NODE_ENV=dev
|
||||
HOST=localhost
|
||||
PORT=3000
|
||||
|
||||
# DATABASE
|
||||
# POSTGRES
|
||||
POSTGRES_URI=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_DB=authentication
|
||||
@ -10,6 +10,11 @@ POSTGRES_DB_TEST=authentication_test
|
||||
POSTGRES_USER=admin
|
||||
POSTGRES_PASSWORD=admin
|
||||
|
||||
# REDIS
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=admin
|
||||
|
||||
# BCRYPT
|
||||
SECRET_PEPPER=github@adhamhaddad
|
||||
SALT_ROUNDS=10
|
||||
@ -17,8 +22,8 @@ SALT_ROUNDS=10
|
||||
# JWT
|
||||
JWT_SECRET_ACCESS_TOKEN=
|
||||
JWT_SECRET_REFRESH_TOKEN=
|
||||
JWT_ACCESS_TOKEN_EXPIRATION=5m
|
||||
JWT_REFRESH_TOKEN_EXPIRATION=1y
|
||||
JWT_ACCESS_TOKEN_EXPIRATION=300
|
||||
JWT_REFRESH_TOKEN_EXPIRATION=86400
|
||||
|
||||
# URL
|
||||
BACKEND_HOST=http://127.0.0.1:8000
|
||||
|
||||
13
README.md
13
README.md
@ -18,6 +18,12 @@ My starter kit includes the following features and benefits:
|
||||
|
||||
I hope that my starter kit can help simplify the process of building Node.js applications with authentication and token management features, and provide a solid foundation for your application development. Please feel free to use and customize the code, and share your feedback and contributions to the repository.
|
||||
|
||||
# High Level Architecture
|
||||
|
||||
Figure 1 represents the authentication architecture of this sample implementation. The architecture is comprised of two logical components; application user, and application services.
|
||||
|
||||
<p align="center"><img src="documents/authenticate.png" alt="Architecture Overview"/>Figure 1: High Level Architecture</p>
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Node v14.15.1 (LTS) or more recent. While older versions can work it is advisable to keep node to latest LTS version
|
||||
@ -78,8 +84,9 @@ Open <http://localhost:3000/> to view it in the browser.
|
||||
|
||||
## Built With
|
||||
|
||||
- [Node](https://nodejs.org) - Javascript Runtime
|
||||
- [Express](https://expressjs.com/) - Javascript API Framework
|
||||
- [PostgreSQL](https://www.postgresql.org/) - Open Source Relational Database
|
||||
- [Node](https://nodejs.org) - Javascript runtime
|
||||
- [Express](https://expressjs.com/) - Javascript API framework
|
||||
- [PostgreSQL](https://www.postgresql.org/) - Open source Relational Database
|
||||
- [Redis](https://redis.io/) - Open source in-memory data store.
|
||||
- [Jasmine](https://jasmine.github.io/) - Testing library
|
||||
- [JWT](https://jwt.io/) - JSON Web Token for generates access and refresh tokens
|
||||
|
||||
BIN
documents/authenticate.png
Normal file
BIN
documents/authenticate.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@ -1,6 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
@ -8,10 +9,12 @@
|
||||
"helmet": "^6.1.5",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"morgan": "^1.10.0",
|
||||
"pg": "^8.10.0"
|
||||
"pg": "^8.10.0",
|
||||
"redis": "^4.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/dotenv": "^8.2.0",
|
||||
"@types/eslint-config-prettier": "^6.11.0",
|
||||
@ -26,7 +29,10 @@
|
||||
"@types/nodemon": "^1.19.2",
|
||||
"@types/pg": "^8.6.6",
|
||||
"@types/prettier": "^2.7.2",
|
||||
"@types/redis": "^4.0.11",
|
||||
"@types/typescript": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
||||
"@typescript-eslint/parser": "^5.59.1",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
|
||||
@ -3,12 +3,12 @@ import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
const database =
|
||||
process.env.ENV === 'dev'
|
||||
process.env.NODE_ENV === 'dev'
|
||||
? process.env.POSTGRES_DB
|
||||
: process.env.POSTGRES_DB_TEST;
|
||||
|
||||
const configs = {
|
||||
env: process.env.ENV,
|
||||
env: process.env.NODE_ENV,
|
||||
host: process.env.HOST,
|
||||
port: Number(process.env.PORT),
|
||||
db_host: process.env.POSTGRES_URI,
|
||||
@ -16,12 +16,15 @@ const configs = {
|
||||
db_name: database,
|
||||
db_user: process.env.POSTGRES_USER,
|
||||
db_password: process.env.POSTGRES_PASSWORD,
|
||||
redis_host: process.env.REDIS_HOST,
|
||||
redis_port: Number(process.env.REDIS_PORT),
|
||||
redis_password: process.env.REDIS_PASSWORD,
|
||||
salt: Number(process.env.SALT_ROUNDS),
|
||||
pepper: process.env.SECRET_PEPPER,
|
||||
access_token: process.env.JWT_SECRET_ACCESS_TOKEN,
|
||||
refresh_token: process.env.JWT_SECRET_REFRESH_TOKEN,
|
||||
access_expires: process.env.JWT_ACCESS_TOKEN_EXPIRATION,
|
||||
refresh_expires: process.env.JWT_REFRESH_TOKEN_EXPIRATION,
|
||||
access_expires: Number(process.env.JWT_ACCESS_TOKEN_EXPIRATION),
|
||||
refresh_expires: Number(process.env.JWT_REFRESH_TOKEN_EXPIRATION),
|
||||
backend_host: process.env.BACKEND_HOST,
|
||||
frontend_host: process.env.FRONTEND_HOST
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createUser } from './register';
|
||||
import { authUser } from './login';
|
||||
import { updatePassword } from './updatePassword';
|
||||
import { refreshToken } from './refreshToken';
|
||||
import { refreshAccessToken } from './refreshAccessToken';
|
||||
|
||||
export { createUser, authUser, updatePassword, refreshToken };
|
||||
export { createUser, authUser, updatePassword, refreshAccessToken };
|
||||
|
||||
@ -1,17 +1,36 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { setAccessToken, setRefreshToken } from '../../utils/token';
|
||||
import Auth from '../../models/auth';
|
||||
import { signAccessToken, signRefreshToken } from '../../utils/token';
|
||||
import configs from '../../configs';
|
||||
|
||||
const auth = new Auth();
|
||||
|
||||
export const authUser = async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Authenticate the user and generate an access token and refresh token
|
||||
const response = await auth.authUser(req.body);
|
||||
const accessToken = await signAccessToken(response);
|
||||
const refreshToken = await signRefreshToken(response);
|
||||
const accessToken = await setAccessToken(response);
|
||||
const refreshToken = await setRefreshToken(response);
|
||||
|
||||
// Set the access token as an HTTP-only cookie
|
||||
res.cookie('accessToken', accessToken, {
|
||||
httpOnly: true,
|
||||
sameSite: 'strict',
|
||||
secure: false,
|
||||
maxAge: configs.access_expires
|
||||
});
|
||||
|
||||
// Set the refresh token as an HTTP-only cookie
|
||||
res.cookie('refreshToken', refreshToken, {
|
||||
httpOnly: true,
|
||||
sameSite: 'strict',
|
||||
secure: false,
|
||||
maxAge: configs.refresh_expires
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
status: true,
|
||||
data: { user: { ...response }, tokens: { accessToken, refreshToken } },
|
||||
data: { user: { ...response }, accessToken },
|
||||
message: 'User authenticated successfully.'
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
57
src/controllers/auth/refreshAccessToken.ts
Normal file
57
src/controllers/auth/refreshAccessToken.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { Request as ExpressRequest, Response } from 'express';
|
||||
import {
|
||||
verifyRefreshToken,
|
||||
setAccessToken,
|
||||
setRefreshToken
|
||||
} from '../../utils/token';
|
||||
import configs from '../../configs';
|
||||
import { DecodedToken } from '../../utils/token';
|
||||
|
||||
interface Request extends ExpressRequest {
|
||||
user?: DecodedToken;
|
||||
}
|
||||
|
||||
export const refreshAccessToken = async (req: Request, res: Response) => {
|
||||
const { refreshToken: token } = req.body;
|
||||
try {
|
||||
// Verify the refresh token
|
||||
const decoded = await verifyRefreshToken(token);
|
||||
// Generate a new access and refresh tokens
|
||||
const { id, first_name, last_name, username, email } = decoded;
|
||||
const accessToken = await setAccessToken({
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
username,
|
||||
email
|
||||
});
|
||||
const refreshToken = await setRefreshToken({
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
username,
|
||||
email
|
||||
});
|
||||
|
||||
res.cookie('accessToken', accessToken, {
|
||||
httpOnly: true,
|
||||
sameSite: 'strict',
|
||||
secure: false,
|
||||
maxAge: configs.access_expires
|
||||
});
|
||||
res.cookie('refreshToken', refreshToken, {
|
||||
httpOnly: true,
|
||||
sameSite: 'strict',
|
||||
secure: false,
|
||||
maxAge: configs.refresh_expires
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
data: { accessToken }
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(401).json({
|
||||
message: (error as Error).message
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,31 +0,0 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { verifyRefreshToken, signAccessToken } from '../../utils/token';
|
||||
|
||||
export const refreshToken = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { refreshToken: token } = req.body;
|
||||
if (!token) throw 'Bad request';
|
||||
|
||||
const payload = await verifyRefreshToken(token);
|
||||
const accessToken = await signAccessToken({
|
||||
// @ts-ignore
|
||||
id: payload?.id,
|
||||
// @ts-ignore
|
||||
first_name: payload?.first_name,
|
||||
// @ts-ignore
|
||||
last_name: payload?.last_name,
|
||||
// @ts-ignore
|
||||
username: payload?.username,
|
||||
// @ts-ignore
|
||||
email: payload?.email
|
||||
});
|
||||
res.status(200).json({
|
||||
data: { accessToken }
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(401).json({
|
||||
status: false,
|
||||
message: error
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,14 +1,14 @@
|
||||
import { Request, Response } from 'express';
|
||||
import User from '../../models/user';
|
||||
import { signAccessToken, signRefreshToken } from '../../utils/token';
|
||||
import { setAccessToken, setRefreshToken } from '../../utils/token';
|
||||
|
||||
const user = new User();
|
||||
|
||||
export const createUser = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const response = await user.createUser(req.body);
|
||||
const accessToken = await signAccessToken(response);
|
||||
const refreshToken = await signRefreshToken(response);
|
||||
const accessToken = await setAccessToken(response);
|
||||
const refreshToken = await setRefreshToken(response);
|
||||
res.status(201).json({
|
||||
status: true,
|
||||
data: { user: { ...response }, tokens: { accessToken, refreshToken } },
|
||||
|
||||
@ -1,17 +1,4 @@
|
||||
import { Pool, PoolClient } from 'pg';
|
||||
import configs from '../configs';
|
||||
import pgClient from './pg';
|
||||
import redisClient from './redis';
|
||||
|
||||
const pool = new Pool({
|
||||
host: configs.db_host,
|
||||
port: configs.db_port,
|
||||
database: configs.db_name,
|
||||
user: configs.db_user,
|
||||
password: configs.db_password
|
||||
});
|
||||
|
||||
export default {
|
||||
connect: async (): Promise<PoolClient> => {
|
||||
const client = await pool.connect();
|
||||
return client;
|
||||
}
|
||||
};
|
||||
export { pgClient, redisClient };
|
||||
|
||||
25
src/database/pg.ts
Normal file
25
src/database/pg.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Pool, PoolClient } from 'pg';
|
||||
import configs from '../configs';
|
||||
|
||||
const pool = new Pool({
|
||||
host: configs.db_host,
|
||||
port: configs.db_port,
|
||||
database: configs.db_name,
|
||||
user: configs.db_user,
|
||||
password: configs.db_password
|
||||
});
|
||||
|
||||
pool.on('connect', () => {
|
||||
console.log('Connected to Postgres.');
|
||||
});
|
||||
|
||||
pool.on('error', (error) => {
|
||||
console.error('Error connecting to Postgres:', error);
|
||||
});
|
||||
|
||||
export default {
|
||||
connect: async (): Promise<PoolClient> => {
|
||||
const client = await pool.connect();
|
||||
return client;
|
||||
}
|
||||
};
|
||||
13
src/database/redis.ts
Normal file
13
src/database/redis.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createClient } from 'redis';
|
||||
|
||||
const client = createClient({ url: 'redis://default@localhost:6379' });
|
||||
|
||||
client.on('connect', () => {
|
||||
console.log('Connected to Redis.');
|
||||
});
|
||||
|
||||
client.on('error', (error) => {
|
||||
console.error('Error connecting to Redis:', error);
|
||||
});
|
||||
|
||||
export default client;
|
||||
@ -1,5 +1,5 @@
|
||||
import database from '../database';
|
||||
import { PoolClient } from 'pg';
|
||||
import { pgClient } from '../database';
|
||||
import { compare, hash as hashPass } from '../utils/password';
|
||||
import { UserType } from './user';
|
||||
|
||||
@ -16,7 +16,7 @@ class Auth {
|
||||
async withConnection<T>(
|
||||
callback: (connection: PoolClient) => Promise<T>
|
||||
): Promise<T> {
|
||||
const connection = await database.connect();
|
||||
const connection = await pgClient.connect();
|
||||
try {
|
||||
return await callback(connection);
|
||||
} catch (error) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import database from '../../database';
|
||||
import { pgClient } from '../../database';
|
||||
import User, { UserType } from '../user';
|
||||
import Auth, { AuthType, PasswordType } from '../auth';
|
||||
import Auth, { PasswordType } from '../auth';
|
||||
|
||||
const auth = new Auth();
|
||||
const user = new User();
|
||||
@ -31,7 +31,7 @@ describe('User Model', () => {
|
||||
} as UserType;
|
||||
|
||||
beforeAll(async () => {
|
||||
const connection = await database.connect();
|
||||
const connection = await pgClient.connect();
|
||||
try {
|
||||
const query = {
|
||||
text: 'DELETE FROM users;\nALTER SEQUENCE users_id_seq RESTART WITH 1'
|
||||
@ -101,7 +101,7 @@ describe('Auth Model', () => {
|
||||
} as PasswordType;
|
||||
|
||||
afterAll(async () => {
|
||||
const connection = await database.connect();
|
||||
const connection = await pgClient.connect();
|
||||
try {
|
||||
const query = {
|
||||
text: 'DELETE FROM users;\nALTER SEQUENCE users_id_seq RESTART WITH 1'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { PoolClient } from 'pg';
|
||||
import database from '../database';
|
||||
import { pgClient } from '../database';
|
||||
import { hash } from '../utils/password';
|
||||
|
||||
export type UserType = {
|
||||
@ -17,7 +17,7 @@ class User {
|
||||
async withConnection<T>(
|
||||
callback: (connection: PoolClient) => Promise<T>
|
||||
): Promise<T> {
|
||||
const connection = await database.connect();
|
||||
const connection = await pgClient.connect();
|
||||
try {
|
||||
return await callback(connection);
|
||||
} catch (error) {
|
||||
|
||||
@ -7,10 +7,9 @@ import {
|
||||
import {
|
||||
createUser,
|
||||
authUser,
|
||||
refreshToken,
|
||||
refreshAccessToken,
|
||||
updatePassword
|
||||
} from '../../controllers/auth';
|
||||
import { checkAccessToken } from '../../utils/token';
|
||||
import { verifyToken } from '../../middlewares/verifyToken';
|
||||
|
||||
const router = Router();
|
||||
@ -19,7 +18,6 @@ router
|
||||
.post('/register', validateRegister, createUser)
|
||||
.post('/login', validateLogin, authUser)
|
||||
.patch('/reset-password', validateUpdatePassword, verifyToken, updatePassword)
|
||||
.post('/refresh-token', refreshToken)
|
||||
.get('/check-token', checkAccessToken);
|
||||
.post('/refresh-token', refreshAccessToken);
|
||||
|
||||
export default router;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import express, { Application } from 'express';
|
||||
import helmet from 'helmet';
|
||||
import morgan from 'morgan';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import cors from 'cors';
|
||||
import os from 'os';
|
||||
import configs from './configs';
|
||||
@ -34,6 +35,7 @@ const corsOptions = {
|
||||
// Middlewares
|
||||
app.use(helmet());
|
||||
app.use(cors(corsOptions));
|
||||
app.use(cookieParser());
|
||||
app.use(morgan('common'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
|
||||
@ -1,179 +0,0 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt, { SignOptions } from 'jsonwebtoken';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import configs from '../configs';
|
||||
import Auth from '../models/auth';
|
||||
|
||||
type Payload = {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
username: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
const privateAccessKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'accessToken',
|
||||
'private.key'
|
||||
);
|
||||
const privateRefreshKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'refreshToken',
|
||||
'private.key'
|
||||
);
|
||||
const publicAccessKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'accessToken',
|
||||
'public.key'
|
||||
);
|
||||
const publicRefreshKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'refreshToken',
|
||||
'public.key'
|
||||
);
|
||||
|
||||
export const signAccessToken = async (payload: Payload) => {
|
||||
const options: SignOptions = {
|
||||
algorithm: 'RS256',
|
||||
expiresIn: configs.access_expires,
|
||||
issuer: 'adhamhaddad',
|
||||
audience: String(payload.id)
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(privateAccessKey, { encoding: 'utf8' }, (err, key) => {
|
||||
if (err) reject(err);
|
||||
jwt.sign(payload, key, options, (err, token) => {
|
||||
if (err) reject(err);
|
||||
resolve(token);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const verifyAccessToken = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const authorization = req.headers.authorization as string;
|
||||
if (!authorization) {
|
||||
return res.status(401).json({
|
||||
status: false,
|
||||
message: 'Not Authorized'
|
||||
});
|
||||
}
|
||||
const token = authorization.split(' ')[1];
|
||||
fs.readFile(publicAccessKey, 'utf8', (err, key) => {
|
||||
if (err) err.message;
|
||||
jwt.verify(token, key, { algorithms: ['RS256'] }, (err, payload) => {
|
||||
if (err)
|
||||
return res.status(401).json({
|
||||
status: false,
|
||||
message: (err as Error).message
|
||||
});
|
||||
return next();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const signRefreshToken = async (payload: Payload) => {
|
||||
const options: SignOptions = {
|
||||
algorithm: 'RS256',
|
||||
expiresIn: configs.refresh_expires,
|
||||
issuer: 'adhamhaddad',
|
||||
audience: String(payload.id)
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(privateRefreshKey, { encoding: 'utf8' }, (err, key) => {
|
||||
if (err) reject(err);
|
||||
jwt.sign(payload, key, options, (err, token) => {
|
||||
if (err) reject(err);
|
||||
resolve(token);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const verifyRefreshToken = async (refreshToken: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(publicRefreshKey, 'utf8', (err, key) => {
|
||||
if (err) reject(err);
|
||||
jwt.verify(
|
||||
refreshToken,
|
||||
key,
|
||||
{ algorithms: ['RS256'] },
|
||||
(err, payload) => {
|
||||
if (err) reject(err);
|
||||
resolve(payload);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const checkAccessToken = async (req: Request, res: Response) => {
|
||||
const auth = new Auth();
|
||||
try {
|
||||
const authorization = req.headers.authorization as string;
|
||||
if (!authorization) {
|
||||
res.status(401).json({
|
||||
status: false,
|
||||
message: 'Not Authorized'
|
||||
});
|
||||
}
|
||||
const token = authorization.split(' ')[1];
|
||||
const authMe = async (id: string) => await auth.authMe(id);
|
||||
fs.readFile(publicAccessKey, 'utf8', (err, key) => {
|
||||
if (err) err.message;
|
||||
jwt.verify(token, key, { algorithms: ['RS256'] }, (err, payload) => {
|
||||
if (err) {
|
||||
const decode = jwt.decode(token, { complete: true });
|
||||
// @ts-ignore
|
||||
const id = decode?.payload?.aud;
|
||||
const responseData = async () => {
|
||||
const response = await authMe(id);
|
||||
const accessToken = await signAccessToken(response);
|
||||
return res.status(200).json({
|
||||
status: true,
|
||||
data: {
|
||||
response,
|
||||
accessToken
|
||||
},
|
||||
message: 'Access token generated successfully.'
|
||||
});
|
||||
};
|
||||
responseData();
|
||||
} else {
|
||||
// @ts-ignore
|
||||
const id = payload.id;
|
||||
const responseData = async () => {
|
||||
const response = await authMe(id);
|
||||
res.status(200).json({
|
||||
data: response
|
||||
});
|
||||
};
|
||||
responseData();
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
status: false,
|
||||
message: (err as Error).message
|
||||
});
|
||||
}
|
||||
};
|
||||
24
src/utils/token/index.ts
Normal file
24
src/utils/token/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { setAccessToken } from './setAccessToken';
|
||||
import { setRefreshToken } from './setRefreshToken';
|
||||
import { verifyAccessToken } from './verifyAccessToken';
|
||||
import { verifyRefreshToken } from './verifyRefreshToken';
|
||||
|
||||
interface Payload {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
username: string;
|
||||
email: string;
|
||||
}
|
||||
interface DecodedToken {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export {
|
||||
setAccessToken,
|
||||
setRefreshToken,
|
||||
verifyAccessToken,
|
||||
verifyRefreshToken,
|
||||
Payload,
|
||||
DecodedToken
|
||||
};
|
||||
40
src/utils/token/setAccessToken.ts
Normal file
40
src/utils/token/setAccessToken.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import jwt, { SignOptions } from 'jsonwebtoken';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import configs from '../../configs';
|
||||
import { redisClient } from '../../database';
|
||||
import { Payload } from '.';
|
||||
|
||||
const privateAccessKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'accessToken',
|
||||
'private.key'
|
||||
);
|
||||
|
||||
export const setAccessToken = async (payload: Payload): Promise<string> => {
|
||||
await redisClient.connect();
|
||||
try {
|
||||
const privateKey = await fs.promises.readFile(privateAccessKey, 'utf8');
|
||||
const options: SignOptions = {
|
||||
algorithm: 'RS256',
|
||||
expiresIn: configs.access_expires,
|
||||
issuer: 'Nodejs-Refresh-Token',
|
||||
audience: `user_id-${payload.id}`,
|
||||
subject: 'access_token'
|
||||
};
|
||||
const token = jwt.sign(payload, privateKey, options);
|
||||
await redisClient.set(`access_token:${payload.id}`, token, {
|
||||
EX: configs.access_expires
|
||||
});
|
||||
return token;
|
||||
} catch (err) {
|
||||
console.log((err as Error).message);
|
||||
throw new Error('Failed to sign JWT');
|
||||
} finally {
|
||||
await redisClient.disconnect();
|
||||
}
|
||||
};
|
||||
39
src/utils/token/setRefreshToken.ts
Normal file
39
src/utils/token/setRefreshToken.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import jwt, { SignOptions } from 'jsonwebtoken';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import configs from '../../configs';
|
||||
import { redisClient } from '../../database';
|
||||
import { Payload } from '.';
|
||||
|
||||
const privateRefreshKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'refreshToken',
|
||||
'private.key'
|
||||
);
|
||||
|
||||
export const setRefreshToken = async (payload: Payload): Promise<string> => {
|
||||
await redisClient.connect();
|
||||
try {
|
||||
const privateKey = await fs.promises.readFile(privateRefreshKey, 'utf8');
|
||||
const options: SignOptions = {
|
||||
algorithm: 'RS256',
|
||||
expiresIn: configs.refresh_expires,
|
||||
issuer: 'Nodejs-Refresh-Token',
|
||||
audience: `user_id-${payload.id}`,
|
||||
subject: 'refresh_token'
|
||||
};
|
||||
const token = jwt.sign(payload, privateKey, options);
|
||||
await redisClient.set(`refresh_token:${payload.id}`, token, {
|
||||
EX: configs.refresh_expires
|
||||
});
|
||||
return token;
|
||||
} catch (err) {
|
||||
throw new Error('Failed to sign JWT');
|
||||
} finally {
|
||||
await redisClient.disconnect();
|
||||
}
|
||||
};
|
||||
89
src/utils/token/verifyAccessToken.ts
Normal file
89
src/utils/token/verifyAccessToken.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { Request as ExpressRequest, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { redisClient } from '../../database';
|
||||
import { DecodedToken } from '.';
|
||||
import { verifyRefreshToken, setAccessToken } from '.';
|
||||
import configs from '../../configs';
|
||||
|
||||
const publicAccessKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'accessToken',
|
||||
'public.key'
|
||||
);
|
||||
interface Request extends ExpressRequest {
|
||||
user?: DecodedToken;
|
||||
}
|
||||
|
||||
export const verifyAccessToken = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
await redisClient.connect();
|
||||
try {
|
||||
const authorization = req.headers.authorization as string;
|
||||
if (!authorization) {
|
||||
return res.status(401).json({
|
||||
message: 'Not Authorized'
|
||||
});
|
||||
}
|
||||
const [bearer, token] = authorization.split(' ');
|
||||
if (bearer !== 'Bearer' || !token) {
|
||||
throw new Error(
|
||||
'Invalid Authorization header format. Format is "Bearer <token>".'
|
||||
);
|
||||
}
|
||||
try {
|
||||
const publicKey = await fs.promises.readFile(publicAccessKey, 'utf8');
|
||||
const decoded = jwt.verify(token, publicKey, {
|
||||
algorithms: ['RS256'],
|
||||
issuer: 'Nodejs-Refresh-Token'
|
||||
}) as DecodedToken;
|
||||
const cachedToken = await redisClient.get(`access_token:${decoded.id}`);
|
||||
if (!cachedToken || cachedToken !== token) {
|
||||
throw new Error('Access token not found or expired');
|
||||
}
|
||||
req.user = { id: decoded.id };
|
||||
await redisClient.disconnect();
|
||||
return next();
|
||||
} catch (err) {
|
||||
await redisClient.disconnect();
|
||||
if ((err as Error).name !== 'TokenExpiredError') {
|
||||
throw new Error('Invalid access token');
|
||||
}
|
||||
|
||||
const refreshToken = req.cookies.refreshToken;
|
||||
if (!refreshToken) {
|
||||
throw new Error('Refresh token missing');
|
||||
}
|
||||
|
||||
const decoded = await verifyRefreshToken(refreshToken);
|
||||
const { id, first_name, last_name, username, email } = decoded;
|
||||
const newAccessToken = await setAccessToken({
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
username,
|
||||
email
|
||||
});
|
||||
|
||||
// Attach user object to request and proceed with new access token
|
||||
req.user = { id: String(decoded.id) };
|
||||
res.cookie('accessToken', newAccessToken, {
|
||||
httpOnly: true,
|
||||
sameSite: 'strict',
|
||||
secure: false,
|
||||
maxAge: configs.access_expires
|
||||
});
|
||||
return next();
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(401).json({ message: (err as Error).message });
|
||||
}
|
||||
};
|
||||
35
src/utils/token/verifyRefreshToken.ts
Normal file
35
src/utils/token/verifyRefreshToken.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { redisClient } from '../../database';
|
||||
import { Payload } from '.';
|
||||
|
||||
const publicRefreshKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'refreshToken',
|
||||
'public.key'
|
||||
);
|
||||
|
||||
export const verifyRefreshToken = async (token: string): Promise<Payload> => {
|
||||
await redisClient.connect();
|
||||
try {
|
||||
const publicKey = await fs.promises.readFile(publicRefreshKey, 'utf8');
|
||||
const decoded = jwt.verify(token, publicKey, {
|
||||
algorithms: ['RS256'],
|
||||
issuer: 'Nodejs-Refresh-Token'
|
||||
}) as Payload;
|
||||
const cachedToken = await redisClient.get(`refresh_token:${decoded.id}`);
|
||||
if (!cachedToken || cachedToken !== token) {
|
||||
throw new Error('Refresh token not found or expired');
|
||||
}
|
||||
return decoded;
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to verify JWT: ${(err as Error).message}`);
|
||||
} finally {
|
||||
await redisClient.disconnect();
|
||||
}
|
||||
};
|
||||
292
yarn.lock
292
yarn.lock
@ -101,12 +101,12 @@
|
||||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/fs.stat@2.0.5":
|
||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
|
||||
"@nodelib/fs.walk@^1.2.8":
|
||||
"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
|
||||
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
||||
@ -114,6 +114,40 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@redis/bloom@1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-1.2.0.tgz#d3fd6d3c0af3ef92f26767b56414a370c7b63b71"
|
||||
integrity sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==
|
||||
|
||||
"@redis/client@1.5.6":
|
||||
version "1.5.6"
|
||||
resolved "https://registry.yarnpkg.com/@redis/client/-/client-1.5.6.tgz#869cc65718d7d5493ef655a71dc40f3bc64a1b28"
|
||||
integrity sha512-dFD1S6je+A47Lj22jN/upVU2fj4huR7S9APd7/ziUXsIXDL+11GPYti4Suv5y8FuXaN+0ZG4JF+y1houEJ7ToA==
|
||||
dependencies:
|
||||
cluster-key-slot "1.1.2"
|
||||
generic-pool "3.9.0"
|
||||
yallist "4.0.0"
|
||||
|
||||
"@redis/graph@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@redis/graph/-/graph-1.1.0.tgz#cc2b82e5141a29ada2cce7d267a6b74baa6dd519"
|
||||
integrity sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==
|
||||
|
||||
"@redis/json@1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@redis/json/-/json-1.0.4.tgz#f372b5f93324e6ffb7f16aadcbcb4e5c3d39bda1"
|
||||
integrity sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==
|
||||
|
||||
"@redis/search@1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@redis/search/-/search-1.1.2.tgz#6a8f66ba90812d39c2457420f859ce8fbd8f3838"
|
||||
integrity sha512-/cMfstG/fOh/SsE+4/BQGeuH/JJloeWuH+qJzM8dbxuWvdWibWAOAHHCZTMPhV3xIlH4/cUEIA8OV5QnYpaVoA==
|
||||
|
||||
"@redis/time-series@1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@redis/time-series/-/time-series-1.0.4.tgz#af85eb080f6934580e4d3b58046026b6c2b18717"
|
||||
integrity sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==
|
||||
|
||||
"@tsconfig/node10@^1.0.7":
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
|
||||
@ -156,6 +190,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/cookie-parser@^1.4.3":
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.3.tgz#3a01df117c5705cf89a84c876b50c5a1fd427a21"
|
||||
integrity sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
|
||||
"@types/cors@^2.8.13":
|
||||
version "2.8.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.13.tgz#b8ade22ba455a1b8cb3b5d3f35910fd204f84f94"
|
||||
@ -211,7 +252,7 @@
|
||||
dependencies:
|
||||
express-validator "*"
|
||||
|
||||
"@types/express@^4.17.17":
|
||||
"@types/express@*", "@types/express@^4.17.17":
|
||||
version "4.17.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4"
|
||||
integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==
|
||||
@ -233,7 +274,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-4.3.1.tgz#2d8ab5601c2fe7d9673dcb157e03f128ab5c5fff"
|
||||
integrity sha512-Vu8l+UGcshYmV1VWwULgnV/2RDbBaO6i2Ptx7nd//oJPIZGhoI1YLST4VKagD2Pq/Bc2/7zvtvhM7F3p4SN7kQ==
|
||||
|
||||
"@types/json-schema@*":
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.9":
|
||||
version "7.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
||||
@ -293,6 +334,18 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/redis@^4.0.11":
|
||||
version "4.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/redis/-/redis-4.0.11.tgz#0bb4c11ac9900a21ad40d2a6768ec6aaf651c0e1"
|
||||
integrity sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg==
|
||||
dependencies:
|
||||
redis "*"
|
||||
|
||||
"@types/semver@^7.3.12":
|
||||
version "7.3.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
|
||||
integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==
|
||||
|
||||
"@types/serve-static@*":
|
||||
version "1.15.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.1.tgz#86b1753f0be4f9a1bee68d459fcda5be4ea52b5d"
|
||||
@ -308,6 +361,90 @@
|
||||
dependencies:
|
||||
typescript "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.59.1":
|
||||
version "5.59.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.1.tgz#9b09ee1541bff1d2cebdcb87e7ce4a4003acde08"
|
||||
integrity sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.4.0"
|
||||
"@typescript-eslint/scope-manager" "5.59.1"
|
||||
"@typescript-eslint/type-utils" "5.59.1"
|
||||
"@typescript-eslint/utils" "5.59.1"
|
||||
debug "^4.3.4"
|
||||
grapheme-splitter "^1.0.4"
|
||||
ignore "^5.2.0"
|
||||
natural-compare-lite "^1.4.0"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/parser@^5.59.1":
|
||||
version "5.59.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.1.tgz#73c2c12127c5c1182d2e5b71a8fa2a85d215cbb4"
|
||||
integrity sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.59.1"
|
||||
"@typescript-eslint/types" "5.59.1"
|
||||
"@typescript-eslint/typescript-estree" "5.59.1"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@5.59.1":
|
||||
version "5.59.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.1.tgz#8a20222719cebc5198618a5d44113705b51fd7fe"
|
||||
integrity sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.59.1"
|
||||
"@typescript-eslint/visitor-keys" "5.59.1"
|
||||
|
||||
"@typescript-eslint/type-utils@5.59.1":
|
||||
version "5.59.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz#63981d61684fd24eda2f9f08c0a47ecb000a2111"
|
||||
integrity sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "5.59.1"
|
||||
"@typescript-eslint/utils" "5.59.1"
|
||||
debug "^4.3.4"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/types@5.59.1":
|
||||
version "5.59.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.1.tgz#03f3fedd1c044cb336ebc34cc7855f121991f41d"
|
||||
integrity sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg==
|
||||
|
||||
"@typescript-eslint/typescript-estree@5.59.1":
|
||||
version "5.59.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz#4aa546d27fd0d477c618f0ca00b483f0ec84c43c"
|
||||
integrity sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.59.1"
|
||||
"@typescript-eslint/visitor-keys" "5.59.1"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/utils@5.59.1":
|
||||
version "5.59.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.1.tgz#d89fc758ad23d2157cfae53f0b429bdf15db9473"
|
||||
integrity sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@types/json-schema" "^7.0.9"
|
||||
"@types/semver" "^7.3.12"
|
||||
"@typescript-eslint/scope-manager" "5.59.1"
|
||||
"@typescript-eslint/types" "5.59.1"
|
||||
"@typescript-eslint/typescript-estree" "5.59.1"
|
||||
eslint-scope "^5.1.1"
|
||||
semver "^7.3.7"
|
||||
|
||||
"@typescript-eslint/visitor-keys@5.59.1":
|
||||
version "5.59.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz#0d96c36efb6560d7fb8eb85de10442c10d8f6058"
|
||||
integrity sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.59.1"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
@ -401,6 +538,11 @@ array-flatten@1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
|
||||
|
||||
array-union@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
|
||||
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
@ -452,7 +594,7 @@ brace-expansion@^1.1.7:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
braces@~3.0.2:
|
||||
braces@^3.0.2, braces@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||
@ -515,6 +657,11 @@ chownr@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
|
||||
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
|
||||
|
||||
cluster-key-slot@1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
|
||||
integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==
|
||||
|
||||
color-convert@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
|
||||
@ -559,11 +706,24 @@ content-type@~1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
|
||||
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
|
||||
|
||||
cookie-parser@^1.4.6:
|
||||
version "1.4.6"
|
||||
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.6.tgz#3ac3a7d35a7a03bbc7e365073a26074824214594"
|
||||
integrity sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==
|
||||
dependencies:
|
||||
cookie "0.4.1"
|
||||
cookie-signature "1.0.6"
|
||||
|
||||
cookie-signature@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
|
||||
|
||||
cookie@0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
|
||||
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
|
||||
|
||||
cookie@0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
||||
@ -598,7 +758,7 @@ debug@2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@^4.1.1, debug@^4.3.2:
|
||||
debug@4, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
@ -642,6 +802,13 @@ diff@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
dir-glob@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
|
||||
integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
|
||||
dependencies:
|
||||
path-type "^4.0.0"
|
||||
|
||||
doctrine@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
|
||||
@ -698,6 +865,14 @@ eslint-plugin-prettier@^4.2.1:
|
||||
dependencies:
|
||||
prettier-linter-helpers "^1.0.0"
|
||||
|
||||
eslint-scope@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
|
||||
dependencies:
|
||||
esrecurse "^4.3.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-scope@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b"
|
||||
@ -780,6 +955,11 @@ esrecurse@^4.3.0:
|
||||
dependencies:
|
||||
estraverse "^5.2.0"
|
||||
|
||||
estraverse@^4.1.1:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||
|
||||
estraverse@^5.1.0, estraverse@^5.2.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
||||
@ -858,6 +1038,17 @@ fast-diff@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
|
||||
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
|
||||
|
||||
fast-glob@^3.2.9:
|
||||
version "3.2.12"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
|
||||
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "^2.0.2"
|
||||
"@nodelib/fs.walk" "^1.2.3"
|
||||
glob-parent "^5.1.2"
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.4"
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
@ -970,6 +1161,11 @@ gauge@^3.0.0:
|
||||
strip-ansi "^6.0.1"
|
||||
wide-align "^1.1.2"
|
||||
|
||||
generic-pool@3.9.0:
|
||||
version "3.9.0"
|
||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.9.0.tgz#36f4a678e963f4fdb8707eab050823abc4e8f5e4"
|
||||
integrity sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==
|
||||
|
||||
get-intrinsic@^1.0.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f"
|
||||
@ -979,6 +1175,13 @@ get-intrinsic@^1.0.2:
|
||||
has "^1.0.3"
|
||||
has-symbols "^1.0.3"
|
||||
|
||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob-parent@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
|
||||
@ -986,13 +1189,6 @@ glob-parent@^6.0.2:
|
||||
dependencies:
|
||||
is-glob "^4.0.3"
|
||||
|
||||
glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob@^7.1.3, glob@^7.1.6:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
||||
@ -1012,6 +1208,18 @@ globals@^13.19.0:
|
||||
dependencies:
|
||||
type-fest "^0.20.2"
|
||||
|
||||
globby@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
|
||||
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
|
||||
dependencies:
|
||||
array-union "^2.1.0"
|
||||
dir-glob "^3.0.1"
|
||||
fast-glob "^3.2.9"
|
||||
ignore "^5.2.0"
|
||||
merge2 "^1.4.1"
|
||||
slash "^3.0.0"
|
||||
|
||||
grapheme-splitter@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
|
||||
@ -1278,11 +1486,24 @@ merge-descriptors@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
|
||||
|
||||
merge2@^1.3.0, merge2@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
|
||||
|
||||
micromatch@^4.0.4:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
|
||||
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
|
||||
dependencies:
|
||||
braces "^3.0.2"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
@ -1358,6 +1579,11 @@ ms@2.1.3, ms@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
natural-compare-lite@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4"
|
||||
integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
@ -1524,6 +1750,11 @@ path-to-regexp@0.1.7:
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
|
||||
|
||||
path-type@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||
|
||||
pg-connection-string@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
|
||||
@ -1575,7 +1806,7 @@ pgpass@1.x:
|
||||
dependencies:
|
||||
split2 "^4.1.0"
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1:
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
@ -1680,6 +1911,18 @@ readdirp@~3.6.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
redis@*, redis@^4.6.5:
|
||||
version "4.6.5"
|
||||
resolved "https://registry.yarnpkg.com/redis/-/redis-4.6.5.tgz#f32fbde44429e96f562bb0c9b1db0143ab8cfa4f"
|
||||
integrity sha512-O0OWA36gDQbswOdUuAhRL6mTZpHFN525HlgZgDaVNgCJIAZR3ya06NTESb0R+TUZ+BFaDpz6NnnVvoMx9meUFg==
|
||||
dependencies:
|
||||
"@redis/bloom" "1.2.0"
|
||||
"@redis/client" "1.5.6"
|
||||
"@redis/graph" "1.1.0"
|
||||
"@redis/json" "1.0.4"
|
||||
"@redis/search" "1.1.2"
|
||||
"@redis/time-series" "1.0.4"
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
@ -1729,7 +1972,7 @@ semver@^6.0.0:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
semver@^7.3.5, semver@^7.3.8:
|
||||
semver@^7.3.5, semver@^7.3.7, semver@^7.3.8:
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0"
|
||||
integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==
|
||||
@ -1813,6 +2056,11 @@ simple-update-notifier@^1.0.7:
|
||||
dependencies:
|
||||
semver "~7.0.0"
|
||||
|
||||
slash@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||
|
||||
split2@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
|
||||
@ -1925,6 +2173,18 @@ ts-node@^10.9.1:
|
||||
v8-compile-cache-lib "^3.0.1"
|
||||
yn "3.1.1"
|
||||
|
||||
tslib@^1.8.1:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
||||
integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
|
||||
dependencies:
|
||||
tslib "^1.8.1"
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||
@ -2034,7 +2294,7 @@ xtend@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
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==
|
||||
|
||||
Loading…
Reference in New Issue
Block a user