mirror of
https://github.com/dsoaress/link-free.git
synced 2026-08-17 06:56:04 +01:00
feat: add user and session
This commit is contained in:
@@ -23,6 +23,9 @@
|
||||
"@prisma/client": "^3.6.0",
|
||||
"@stitches/react": "^1.2.6",
|
||||
"axios": "^0.24.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"dotenv": "^10.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "12.0.7",
|
||||
"next-connect": "^0.11.0",
|
||||
@@ -35,6 +38,8 @@
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "15.0.0",
|
||||
"@commitlint/config-conventional": "15.0.0",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/jsonwebtoken": "^8.5.6",
|
||||
"@types/lodash": "^4.14.178",
|
||||
"@types/node": "^16.11.12",
|
||||
"@types/react": "17.0.37",
|
||||
|
||||
@@ -11,3 +11,22 @@ model Data {
|
||||
id Int @id @default(autoincrement())
|
||||
data String
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
role Role
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(cuid())
|
||||
userId Int
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
enum Role {
|
||||
ADMIN
|
||||
EDITOR
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import { hashSync } from 'bcryptjs'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
dotenv.config({
|
||||
path: './.env'
|
||||
})
|
||||
|
||||
async function main() {
|
||||
const data = {
|
||||
settings: {
|
||||
@@ -62,9 +68,7 @@ async function main() {
|
||||
}
|
||||
|
||||
const hasData = await prisma.data.findUnique({
|
||||
where: {
|
||||
id: 1
|
||||
}
|
||||
where: { id: 1 }
|
||||
})
|
||||
|
||||
if (!hasData) {
|
||||
@@ -77,6 +81,26 @@ async function main() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const hasUser = await prisma.user.findFirst()
|
||||
|
||||
if (!hasUser) {
|
||||
console.log(`Start seeding user...`)
|
||||
|
||||
const { USERNAME, PASSWORD } = process.env
|
||||
|
||||
if (!USERNAME || !PASSWORD) {
|
||||
throw new Error('USERNAME and PASSWORD env variables are required')
|
||||
}
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
username: USERNAME,
|
||||
password: hashSync(PASSWORD, 10),
|
||||
role: 'ADMIN'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
58
src/pages/api/auth/index.ts
Normal file
58
src/pages/api/auth/index.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Prisma } from '@prisma/client'
|
||||
import { compare } from 'bcryptjs'
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import nc from 'next-connect'
|
||||
|
||||
import { prisma } from '../../../services/prisma'
|
||||
import { createSession } from '../../../utils/createSession'
|
||||
import { ExceptionError } from '../../../utils/error'
|
||||
|
||||
const handler = nc<NextApiRequest, NextApiResponse>({
|
||||
onNoMatch: (_req, res) => {
|
||||
res.status(404).json({ error: 'Not found' })
|
||||
},
|
||||
onError: (err, _req, res) => {
|
||||
if (err instanceof ExceptionError) {
|
||||
res.status(err.status).json({
|
||||
status: err.status,
|
||||
error: err.message
|
||||
})
|
||||
} else if (err instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
res.status(400).json({
|
||||
status: 400,
|
||||
error: err.message
|
||||
})
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
}).post(async (req, res) => {
|
||||
const { username, password } = req.body
|
||||
|
||||
if (!username || !password) {
|
||||
throw new ExceptionError('Username and password are required')
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { username }
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
throw new ExceptionError('Credentials are invalid', 401)
|
||||
}
|
||||
|
||||
const isValidPassword = await compare(password, user.password)
|
||||
|
||||
if (!isValidPassword) {
|
||||
throw new ExceptionError('Credentials are invalid', 401)
|
||||
}
|
||||
|
||||
const { token, refreshToken } = await createSession(user)
|
||||
|
||||
res.status(200).json({ token, refreshToken })
|
||||
})
|
||||
|
||||
export default handler
|
||||
73
src/pages/api/auth/refresh-token.ts
Normal file
73
src/pages/api/auth/refresh-token.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Prisma } from '@prisma/client'
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import nc from 'next-connect'
|
||||
|
||||
import { prisma } from '../../../services/prisma'
|
||||
import { createSession } from '../../../utils/createSession'
|
||||
import { ExceptionError } from '../../../utils/error'
|
||||
|
||||
const handler = nc<NextApiRequest, NextApiResponse>({
|
||||
onNoMatch: (_req, res) => {
|
||||
res.status(404).json({ error: 'Not found' })
|
||||
},
|
||||
onError: (err, _req, res) => {
|
||||
if (err instanceof ExceptionError) {
|
||||
res.status(err.status).json({
|
||||
status: err.status,
|
||||
error: err.message
|
||||
})
|
||||
} else if (err instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
res.status(400).json({
|
||||
status: 400,
|
||||
error: err.message
|
||||
})
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
}).post(async (req, res) => {
|
||||
const { refreshToken } = req.body
|
||||
|
||||
if (!refreshToken) {
|
||||
throw new ExceptionError('Refresh Token required')
|
||||
}
|
||||
|
||||
const session = await prisma.session.findUnique({
|
||||
where: { id: refreshToken }
|
||||
})
|
||||
|
||||
if (!session) {
|
||||
throw new ExceptionError('Refresh Token are invalid', 401)
|
||||
}
|
||||
|
||||
const isSessionExpired = new Date(session.expiresAt) < new Date()
|
||||
|
||||
if (isSessionExpired) {
|
||||
await prisma.session.delete({
|
||||
where: { id: refreshToken }
|
||||
})
|
||||
|
||||
throw new ExceptionError('Refresh Token are expired', 401)
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: session.userId }
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
throw new ExceptionError('User not found', 401)
|
||||
}
|
||||
|
||||
const tokens = await createSession(user)
|
||||
|
||||
await prisma.session.delete({
|
||||
where: { id: refreshToken }
|
||||
})
|
||||
|
||||
res.status(200).json({ token: tokens.token, refreshToken: tokens.refreshToken })
|
||||
})
|
||||
|
||||
export default handler
|
||||
@@ -1,14 +1,31 @@
|
||||
import { Prisma } from '@prisma/client'
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import nc from 'next-connect'
|
||||
|
||||
import { prisma } from '../../services/prisma'
|
||||
import { ExceptionError } from '../../utils/error'
|
||||
|
||||
const handler = nc<NextApiRequest, NextApiResponse>({
|
||||
onNoMatch: (_req, res) => {
|
||||
res.status(404).json({ error: 'Not found' })
|
||||
},
|
||||
onError: (err, _req, res) => {
|
||||
res.status(500).json({ error: err.message })
|
||||
if (err instanceof ExceptionError) {
|
||||
res.status(err.status).json({
|
||||
status: err.status,
|
||||
error: err.message
|
||||
})
|
||||
} else if (err instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
res.status(400).json({
|
||||
status: 400,
|
||||
error: err.message
|
||||
})
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.get(async (_req, res) => {
|
||||
@@ -17,7 +34,7 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
||||
})
|
||||
|
||||
if (!response) {
|
||||
throw new Error('No data found')
|
||||
throw new ExceptionError('No data found')
|
||||
}
|
||||
|
||||
res.status(200).json(response?.data)
|
||||
@@ -27,7 +44,7 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
||||
const { data } = req.body
|
||||
|
||||
if (!data) {
|
||||
throw new Error('No data provided')
|
||||
throw new ExceptionError('No data provided')
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -38,8 +55,7 @@ const handler = nc<NextApiRequest, NextApiResponse>({
|
||||
|
||||
res.status(200).json(response)
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
res.status(500).json({ error: err.message })
|
||||
throw new ExceptionError(err)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
144
src/pages/api/users/[id].ts
Normal file
144
src/pages/api/users/[id].ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { Prisma } from '@prisma/client'
|
||||
import { compare, hashSync } from 'bcryptjs'
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import nc from 'next-connect'
|
||||
|
||||
import { prisma } from '../../../services/prisma'
|
||||
import { ExceptionError } from '../../../utils/error'
|
||||
|
||||
const handler = nc<NextApiRequest, NextApiResponse>({
|
||||
onNoMatch: (_req, res) => {
|
||||
res.status(404).json({ error: 'Not found' })
|
||||
},
|
||||
onError: (err, _req, res) => {
|
||||
if (err instanceof ExceptionError) {
|
||||
res.status(err.status).json({
|
||||
status: err.status,
|
||||
error: err.message
|
||||
})
|
||||
} else if (err instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
res.status(400).json({
|
||||
status: 400,
|
||||
error: err.message
|
||||
})
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.get(async (req, res) => {
|
||||
const { id } = req.query
|
||||
|
||||
if (!id) {
|
||||
throw new ExceptionError('No id provided')
|
||||
}
|
||||
|
||||
const response = await prisma.user.findUnique({
|
||||
where: { id: +id },
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
role: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!response) {
|
||||
throw new ExceptionError('No data found', 404)
|
||||
}
|
||||
|
||||
res.status(200).json(response)
|
||||
})
|
||||
|
||||
.patch(async (req, res) => {
|
||||
const { id } = req.query
|
||||
const { username, password, newPassword, role } = req.body
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: +id }
|
||||
})
|
||||
|
||||
let updatedUser = {}
|
||||
|
||||
if (!user) {
|
||||
throw new ExceptionError('No user found', 404)
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
throw new ExceptionError('No id provided')
|
||||
}
|
||||
|
||||
if (username) {
|
||||
const usernameExists = await prisma.user.findUnique({
|
||||
where: { username }
|
||||
})
|
||||
|
||||
if (usernameExists && usernameExists.id !== +id) {
|
||||
throw new ExceptionError('Username already exists')
|
||||
}
|
||||
|
||||
updatedUser = { ...updatedUser, username }
|
||||
}
|
||||
|
||||
if (password && newPassword) {
|
||||
const isValidPassword = await compare(password, user.password)
|
||||
|
||||
if (!isValidPassword) {
|
||||
throw new ExceptionError('Password does not match')
|
||||
}
|
||||
|
||||
updatedUser = { password: hashSync(newPassword, 10) }
|
||||
}
|
||||
|
||||
if (role) {
|
||||
if (!['ADMIN', 'EDITOR'].includes(role)) {
|
||||
throw new ExceptionError('Role must be either ADMIN or EDITOR')
|
||||
}
|
||||
|
||||
if (user.role === 'ADMIN' && role === 'EDITOR') {
|
||||
throw new ExceptionError('Cannot downgrade admin role', 403)
|
||||
}
|
||||
|
||||
if (user.role === 'EDITOR') {
|
||||
throw new ExceptionError('Cannot change role', 403)
|
||||
}
|
||||
|
||||
updatedUser = { ...updatedUser, role }
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await prisma.data.update({
|
||||
where: { id: +id },
|
||||
data: { ...updatedUser }
|
||||
})
|
||||
|
||||
res.status(200).json(response)
|
||||
} catch (err: any) {
|
||||
throw new ExceptionError(err)
|
||||
}
|
||||
})
|
||||
|
||||
.delete(async (req, res) => {
|
||||
const { id } = req.query
|
||||
|
||||
if (!id) {
|
||||
throw new ExceptionError('No id provided')
|
||||
}
|
||||
|
||||
if (id === '1') {
|
||||
throw new ExceptionError('Cannot delete admin', 403)
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.data.delete({
|
||||
where: { id: +id }
|
||||
})
|
||||
|
||||
res.status(204).end()
|
||||
} catch (err: any) {
|
||||
throw new ExceptionError(err)
|
||||
}
|
||||
})
|
||||
|
||||
export default handler
|
||||
84
src/pages/api/users/index.ts
Normal file
84
src/pages/api/users/index.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Prisma } from '@prisma/client'
|
||||
import { hashSync } from 'bcryptjs'
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import nc from 'next-connect'
|
||||
|
||||
import { prisma } from '../../../services/prisma'
|
||||
import { ExceptionError } from '../../../utils/error'
|
||||
|
||||
const handler = nc<NextApiRequest, NextApiResponse>({
|
||||
onNoMatch: (_req, res) => {
|
||||
res.status(404).json({ error: 'Not found' })
|
||||
},
|
||||
onError: (err, _req, res) => {
|
||||
if (err instanceof ExceptionError) {
|
||||
res.status(err.status).json({
|
||||
status: err.status,
|
||||
error: err.message
|
||||
})
|
||||
} else if (err instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
res.status(400).json({
|
||||
status: 400,
|
||||
error: err.message
|
||||
})
|
||||
} else {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.get(async (_req, res) => {
|
||||
const response = await prisma.user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
role: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!response) {
|
||||
throw new ExceptionError('No data found')
|
||||
}
|
||||
|
||||
res.status(200).json(response)
|
||||
})
|
||||
|
||||
.post(async (req, res) => {
|
||||
const { username, password, role } = req.body
|
||||
|
||||
if (!username || !password) {
|
||||
throw new ExceptionError('Username and password are required')
|
||||
}
|
||||
|
||||
if (!['ADMIN', 'EDITOR'].includes(role)) {
|
||||
throw new ExceptionError('Role must be either ADMIN or EDITOR')
|
||||
}
|
||||
|
||||
// TODO implement role validation
|
||||
|
||||
const usernameExists = await prisma.user.findUnique({
|
||||
where: { username }
|
||||
})
|
||||
|
||||
if (usernameExists) {
|
||||
throw new ExceptionError('Username already exists')
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
password: hashSync(password, 10),
|
||||
role: role ? role : 'EDITOR'
|
||||
}
|
||||
})
|
||||
|
||||
res.status(200).json(response)
|
||||
} catch (err: any) {
|
||||
throw new ExceptionError(err)
|
||||
}
|
||||
})
|
||||
|
||||
export default handler
|
||||
34
src/utils/createSession.ts
Normal file
34
src/utils/createSession.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { User } from '@prisma/client'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
import { prisma } from '../services/prisma'
|
||||
import { ExceptionError } from './error'
|
||||
|
||||
const EXPIRES_IN = 30 // days
|
||||
const EXPIRES_IN_MS = EXPIRES_IN * 24 * 60 * 60 * 1000
|
||||
|
||||
export async function createSession(user: User) {
|
||||
const { JWT_SECRET } = process.env
|
||||
|
||||
if (!JWT_SECRET) {
|
||||
throw new ExceptionError('JWT_SECRET is not defined')
|
||||
}
|
||||
|
||||
try {
|
||||
const session = await prisma.session.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
expiresAt: new Date(Date.now() + EXPIRES_IN_MS)
|
||||
}
|
||||
})
|
||||
|
||||
const payload = { role: user.role, sub: user.id }
|
||||
const token = jwt.sign(payload, JWT_SECRET, {
|
||||
expiresIn: '15m'
|
||||
})
|
||||
|
||||
return { token, refreshToken: session.id }
|
||||
} catch (err: any) {
|
||||
throw new ExceptionError(err)
|
||||
}
|
||||
}
|
||||
9
src/utils/error.ts
Normal file
9
src/utils/error.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export class ExceptionError extends Error {
|
||||
status: number
|
||||
|
||||
constructor(message: string, status: number = 400) {
|
||||
super(message)
|
||||
this.name = 'ExceptionError'
|
||||
this.status = status
|
||||
}
|
||||
}
|
||||
104
yarn.lock
104
yarn.lock
@@ -450,11 +450,23 @@
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e"
|
||||
integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==
|
||||
|
||||
"@types/bcryptjs@^2.4.2":
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/bcryptjs/-/bcryptjs-2.4.2.tgz#e3530eac9dd136bfdfb0e43df2c4c5ce1f77dfae"
|
||||
integrity sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==
|
||||
|
||||
"@types/json5@^0.0.29":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/jsonwebtoken@^8.5.6":
|
||||
version "8.5.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.6.tgz#1913e5a61e70a192c5a444623da4901a7b1a9d42"
|
||||
integrity sha512-+P3O/xC7nzVizIi5VbF34YtqSonFsdnbXBnWUCYRiKOi1f9gA4sEFvXkrGr/QVV23IbMYvcoerI7nnhDUiWXRQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/lodash@^4.14.178":
|
||||
version "4.14.178"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8"
|
||||
@@ -800,6 +812,11 @@ base64-js@^1.0.2, base64-js@^1.3.1:
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
bcryptjs@^2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
|
||||
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
|
||||
|
||||
big.js@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||
@@ -921,6 +938,11 @@ browserslist@4.16.6:
|
||||
escalade "^3.1.1"
|
||||
node-releases "^1.1.71"
|
||||
|
||||
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 sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
@@ -1466,6 +1488,18 @@ dot-prop@^5.1.0:
|
||||
dependencies:
|
||||
is-obj "^2.0.0"
|
||||
|
||||
dotenv@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
|
||||
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
|
||||
|
||||
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"
|
||||
|
||||
electron-to-chromium@^1.3.723:
|
||||
version "1.4.16"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.16.tgz#38ddecc616385e6f101359d1b978c802664157d2"
|
||||
@@ -2598,6 +2632,22 @@ jsonparse@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
|
||||
integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
|
||||
|
||||
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"
|
||||
|
||||
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b"
|
||||
@@ -2606,6 +2656,23 @@ jsonparse@^1.2.0:
|
||||
array-includes "^3.1.3"
|
||||
object.assign "^4.1.2"
|
||||
|
||||
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"
|
||||
|
||||
kind-of@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||
@@ -2711,11 +2778,46 @@ lodash.get@^4:
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
|
||||
|
||||
lodash.isboolean@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
|
||||
|
||||
lodash.isinteger@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||
integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
|
||||
|
||||
lodash.isnumber@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
||||
|
||||
lodash.sortby@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
@@ -3736,7 +3838,7 @@ scheduler@^0.20.2:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
"semver@2 || 3 || 4 || 5":
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.6.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
||||
Reference in New Issue
Block a user