mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-22 09:57:00 +01:00
Switch to json web tokens.
This commit is contained in:
parent
5219582803
commit
cb0c912c5b
@ -1,31 +1,38 @@
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { v5 as uuid, v4 } from 'uuid';
|
import { v5 } from 'uuid';
|
||||||
import Cryptr from 'cryptr';
|
import jwt from 'jsonwebtoken';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
|
||||||
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
||||||
|
|
||||||
const cryptr = new Cryptr(hash(process.env.HASH_SALT, process.env.DATABASE_URL));
|
export function sha256(...args) {
|
||||||
|
return crypto.createHash('sha256').update(args.join('')).digest('hex');
|
||||||
export function md5(s) {
|
|
||||||
return crypto.createHash('md5').update(s).digest('hex');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hash(...args) {
|
export function secret() {
|
||||||
return uuid(args.join(''), md5(process.env.HASH_SALT));
|
return sha256(process.env.HASH_SALT);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validHash(s) {
|
export function uuid(...args) {
|
||||||
|
return v5(args.join(''), v5(process.env.HASH_SALT, v5.DNS));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function random(n = 64) {
|
||||||
|
return crypto.randomBytes(n).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidHash(s) {
|
||||||
return UUID_REGEX.test(s);
|
return UUID_REGEX.test(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encrypt(s) {
|
export async function createToken(payload, options) {
|
||||||
return cryptr.encrypt(s);
|
return jwt.sign(payload, secret(), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decrypt(s) {
|
export async function parseToken(token, options) {
|
||||||
return cryptr.decrypt(s);
|
return jwt.verify(token, secret(), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function random() {
|
export function checkPassword(password, hash) {
|
||||||
return v4();
|
return bcrypt.compare(password, hash);
|
||||||
}
|
}
|
||||||
|
10
lib/db.js
10
lib/db.js
@ -95,3 +95,13 @@ export async function saveEvent(website_id, session_id, url, event_type, event_v
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAccount(username = '') {
|
||||||
|
return runQuery(
|
||||||
|
prisma.account.findOne({
|
||||||
|
where: {
|
||||||
|
username,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,50 +1,52 @@
|
|||||||
import { getWebsite, getSession, createSession } from 'lib/db';
|
import { getWebsite, getSession, createSession } from 'lib/db';
|
||||||
import { getCountry, getDevice, getIpAddress, isValidSession } from 'lib/utils';
|
import { getCountry, getDevice, getIpAddress } from 'lib/utils';
|
||||||
import { hash } from 'lib/crypto';
|
import { uuid, parseToken, isValidHash } from 'lib/crypto';
|
||||||
|
|
||||||
export default async req => {
|
export default async req => {
|
||||||
const { payload } = req.body;
|
const { payload } = req.body;
|
||||||
const { session } = payload;
|
const { website: website_uuid, hostname, screen, language, session } = payload;
|
||||||
|
|
||||||
if (isValidSession(session)) {
|
if (!isValidHash(website_uuid)) {
|
||||||
return session;
|
throw new Error(`Invalid website: ${website_uuid}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ip = getIpAddress(req);
|
try {
|
||||||
const { userAgent, browser, os } = getDevice(req);
|
return await parseToken(session);
|
||||||
const country = await getCountry(req, ip);
|
} catch {
|
||||||
const { website: website_uuid, hostname, screen, language } = payload;
|
const ip = getIpAddress(req);
|
||||||
|
const { userAgent, browser, os } = getDevice(req);
|
||||||
|
const country = await getCountry(req, ip);
|
||||||
|
|
||||||
if (website_uuid) {
|
if (website_uuid) {
|
||||||
const website = await getWebsite(website_uuid);
|
const website = await getWebsite(website_uuid);
|
||||||
|
|
||||||
if (website) {
|
if (website) {
|
||||||
const { website_id } = website;
|
const { website_id } = website;
|
||||||
const session_uuid = hash(website_id, hostname, ip, userAgent, os);
|
const session_uuid = uuid(website_id, hostname, ip, userAgent, os);
|
||||||
|
|
||||||
let session = await getSession(session_uuid);
|
let session = await getSession(session_uuid);
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
session = await createSession(website_id, {
|
session = await createSession(website_id, {
|
||||||
|
session_uuid,
|
||||||
|
hostname,
|
||||||
|
browser,
|
||||||
|
os,
|
||||||
|
screen,
|
||||||
|
language,
|
||||||
|
country,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { session_id } = session;
|
||||||
|
|
||||||
|
return {
|
||||||
|
website_id,
|
||||||
|
website_uuid,
|
||||||
|
session_id,
|
||||||
session_uuid,
|
session_uuid,
|
||||||
hostname,
|
};
|
||||||
browser,
|
|
||||||
os,
|
|
||||||
screen,
|
|
||||||
language,
|
|
||||||
country,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { session_id } = session;
|
|
||||||
|
|
||||||
return [
|
|
||||||
website_id,
|
|
||||||
website_uuid,
|
|
||||||
session_id,
|
|
||||||
session_uuid,
|
|
||||||
hash(website_id, website_uuid, session_id, session_uuid),
|
|
||||||
].join(':');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
20
lib/utils.js
20
lib/utils.js
@ -1,9 +1,8 @@
|
|||||||
import requestIp from 'request-ip';
|
import requestIp from 'request-ip';
|
||||||
import { browserName, detectOS } from 'detect-browser';
|
import { browserName, detectOS } from 'detect-browser';
|
||||||
|
import isLocalhost from 'is-localhost-ip';
|
||||||
import maxmind from 'maxmind';
|
import maxmind from 'maxmind';
|
||||||
import geolite2 from 'geolite2-redist';
|
import geolite2 from 'geolite2-redist';
|
||||||
import isLocalhost from 'is-localhost-ip';
|
|
||||||
import { hash } from './crypto';
|
|
||||||
|
|
||||||
export function getIpAddress(req) {
|
export function getIpAddress(req) {
|
||||||
// Cloudflare
|
// Cloudflare
|
||||||
@ -44,20 +43,3 @@ export async function getCountry(req, ip) {
|
|||||||
|
|
||||||
return result.country.iso_code;
|
return result.country.iso_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseSession(session) {
|
|
||||||
const [website_id, website_uuid, session_id, session_uuid, sig] = (session || '').split(':');
|
|
||||||
return {
|
|
||||||
website_id: parseInt(website_id),
|
|
||||||
website_uuid,
|
|
||||||
session_id: parseInt(session_id),
|
|
||||||
session_uuid,
|
|
||||||
sig,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isValidSession(session) {
|
|
||||||
const { website_id, website_uuid, session_id, session_uuid, sig } = parseSession(session);
|
|
||||||
|
|
||||||
return hash(website_id, website_uuid, session_id, session_uuid) === sig;
|
|
||||||
}
|
|
||||||
|
@ -29,17 +29,18 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "2.2.2",
|
"@prisma/client": "2.2.2",
|
||||||
|
"base64url": "^3.0.1",
|
||||||
"bcrypt": "^5.0.0",
|
"bcrypt": "^5.0.0",
|
||||||
"chart.js": "^2.9.3",
|
"chart.js": "^2.9.3",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"cookie": "^0.4.1",
|
"cookie": "^0.4.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cryptr": "^6.0.2",
|
|
||||||
"date-fns": "^2.14.0",
|
"date-fns": "^2.14.0",
|
||||||
"detect-browser": "^5.1.1",
|
"detect-browser": "^5.1.1",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"geolite2-redist": "^1.0.7",
|
"geolite2-redist": "^1.0.7",
|
||||||
"is-localhost-ip": "^1.4.0",
|
"is-localhost-ip": "^1.4.0",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
"maxmind": "^4.1.3",
|
"maxmind": "^4.1.3",
|
||||||
"next": "9.3.5",
|
"next": "9.3.5",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
import { serialize } from 'cookie';
|
import { serialize } from 'cookie';
|
||||||
import { hash, random, encrypt } from 'lib/crypto';
|
import { checkPassword, createToken, secret } from 'lib/crypto';
|
||||||
|
import { getAccount } from 'lib/db';
|
||||||
|
|
||||||
export default (req, res) => {
|
export default async (req, res) => {
|
||||||
const { password } = req.body;
|
const { username, password } = req.body;
|
||||||
|
|
||||||
if (password === process.env.PASSWORD) {
|
const account = await getAccount(username);
|
||||||
|
|
||||||
|
if (account && (await checkPassword(password, account.password))) {
|
||||||
|
const { user_id, username, is_admin } = account;
|
||||||
|
const token = await createToken({ user_id, username, is_admin });
|
||||||
const expires = new Date(Date.now() + 31536000000);
|
const expires = new Date(Date.now() + 31536000000);
|
||||||
const id = random();
|
const cookie = serialize('umami.auth', token, { expires, httpOnly: true });
|
||||||
const value = encrypt(`${id}:${hash(id)}`);
|
|
||||||
|
|
||||||
const cookie = serialize('umami.auth', value, { expires, httpOnly: true });
|
|
||||||
|
|
||||||
res.setHeader('Set-Cookie', [cookie]);
|
res.setHeader('Set-Cookie', [cookie]);
|
||||||
res.status(200).send('');
|
|
||||||
|
res.status(200).send({ token });
|
||||||
} else {
|
} else {
|
||||||
res.status(401).send('');
|
res.status(401).send('');
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { parseSession } from 'lib/utils';
|
|
||||||
import { savePageView, saveEvent } from 'lib/db';
|
import { savePageView, saveEvent } from 'lib/db';
|
||||||
import { allowPost } from 'lib/middleware';
|
import { allowPost } from 'lib/middleware';
|
||||||
import checkSession from 'lib/session';
|
import checkSession from 'lib/session';
|
||||||
|
import { createToken } from 'lib/crypto';
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
await allowPost(req, res);
|
await allowPost(req, res);
|
||||||
|
|
||||||
const session = await checkSession(req);
|
const session = await checkSession(req);
|
||||||
|
|
||||||
const { website_id, session_id } = parseSession(session);
|
const { website_id, session_id } = session;
|
||||||
const { type, payload } = req.body;
|
const { type, payload } = req.body;
|
||||||
let ok = 1;
|
let ok = 1;
|
||||||
|
|
||||||
@ -26,5 +26,7 @@ export default async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).json({ ok, session });
|
const token = await createToken(session);
|
||||||
|
|
||||||
|
res.status(200).json({ ok, session: token });
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,16 @@ datasource db {
|
|||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model account {
|
||||||
|
created_at DateTime? @default(now())
|
||||||
|
is_admin Boolean @default(false)
|
||||||
|
password String
|
||||||
|
updated_at DateTime? @default(now())
|
||||||
|
user_id Int @default(autoincrement()) @id
|
||||||
|
username String @unique
|
||||||
|
website website[]
|
||||||
|
}
|
||||||
|
|
||||||
model event {
|
model event {
|
||||||
created_at DateTime? @default(now())
|
created_at DateTime? @default(now())
|
||||||
event_id Int @default(autoincrement()) @id
|
event_id Int @default(autoincrement()) @id
|
||||||
@ -19,6 +29,8 @@ model event {
|
|||||||
website website @relation(fields: [website_id], references: [website_id])
|
website website @relation(fields: [website_id], references: [website_id])
|
||||||
|
|
||||||
@@index([created_at], name: "event_created_at_idx")
|
@@index([created_at], name: "event_created_at_idx")
|
||||||
|
@@index([session_id], name: "event_session_id_idx")
|
||||||
|
@@index([website_id], name: "event_website_id_idx")
|
||||||
}
|
}
|
||||||
|
|
||||||
model pageview {
|
model pageview {
|
||||||
@ -32,6 +44,8 @@ model pageview {
|
|||||||
website website @relation(fields: [website_id], references: [website_id])
|
website website @relation(fields: [website_id], references: [website_id])
|
||||||
|
|
||||||
@@index([created_at], name: "pageview_created_at_idx")
|
@@index([created_at], name: "pageview_created_at_idx")
|
||||||
|
@@index([session_id], name: "pageview_session_id_idx")
|
||||||
|
@@index([website_id], name: "pageview_website_id_idx")
|
||||||
}
|
}
|
||||||
|
|
||||||
model session {
|
model session {
|
||||||
@ -50,13 +64,16 @@ model session {
|
|||||||
pageview pageview[]
|
pageview pageview[]
|
||||||
|
|
||||||
@@index([created_at], name: "session_created_at_idx")
|
@@index([created_at], name: "session_created_at_idx")
|
||||||
|
@@index([website_id], name: "session_website_id_idx")
|
||||||
}
|
}
|
||||||
|
|
||||||
model website {
|
model website {
|
||||||
created_at DateTime? @default(now())
|
created_at DateTime? @default(now())
|
||||||
hostname String
|
hostname String
|
||||||
|
user_id Int
|
||||||
website_id Int @default(autoincrement()) @id
|
website_id Int @default(autoincrement()) @id
|
||||||
website_uuid String @unique
|
website_uuid String @unique
|
||||||
|
account account @relation(fields: [user_id], references: [user_id])
|
||||||
event event[]
|
event event[]
|
||||||
pageview pageview[]
|
pageview pageview[]
|
||||||
session session[]
|
session session[]
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
|
create table account (
|
||||||
|
user_id serial primary key,
|
||||||
|
username varchar(255) unique not null,
|
||||||
|
password varchar(60) not null,
|
||||||
|
is_admin bool not null default false,
|
||||||
|
created_at timestamp with time zone default current_timestamp,
|
||||||
|
updated_at timestamp with time zone default current_timestamp
|
||||||
|
);
|
||||||
|
|
||||||
create table website (
|
create table website (
|
||||||
website_id serial primary key,
|
website_id serial primary key,
|
||||||
website_uuid uuid unique not null,
|
website_uuid uuid unique not null,
|
||||||
|
user_id int not null references account(user_id) on delete cascade,
|
||||||
hostname varchar(100) not null,
|
hostname varchar(100) not null,
|
||||||
created_at timestamp with time zone default current_timestamp
|
created_at timestamp with time zone default current_timestamp
|
||||||
);
|
);
|
||||||
@ -37,6 +47,8 @@ create table event (
|
|||||||
event_value varchar(50) not null
|
event_value varchar(50) not null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create index on account(username);
|
||||||
|
|
||||||
create index on session(created_at);
|
create index on session(created_at);
|
||||||
create index on session(website_id);
|
create index on session(website_id);
|
||||||
|
|
||||||
|
90
yarn.lock
90
yarn.lock
@ -1937,6 +1937,11 @@ base64-js@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||||
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
|
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
|
||||||
|
|
||||||
|
base64url@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
|
||||||
|
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
|
||||||
|
|
||||||
base@^0.11.1:
|
base@^0.11.1:
|
||||||
version "0.11.2"
|
version "0.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
|
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
|
||||||
@ -2129,6 +2134,11 @@ buble@^0.20.0:
|
|||||||
minimist "^1.2.5"
|
minimist "^1.2.5"
|
||||||
regexpu-core "4.5.4"
|
regexpu-core "4.5.4"
|
||||||
|
|
||||||
|
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:
|
buffer-from@^1.0.0:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||||
@ -2759,11 +2769,6 @@ crypto-browserify@^3.11.0:
|
|||||||
randombytes "^2.0.0"
|
randombytes "^2.0.0"
|
||||||
randomfill "^1.0.3"
|
randomfill "^1.0.3"
|
||||||
|
|
||||||
cryptr@^6.0.2:
|
|
||||||
version "6.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/cryptr/-/cryptr-6.0.2.tgz#3f9e97f825ffb93f425eb24068efbb6a652bf947"
|
|
||||||
integrity sha512-1TRHI4bmuLIB8WgkH9eeYXzhEg1T4tonO4vVaMBKKde8Dre51J68nAgTVXTwMYXAf7+mV2gBCkm/9wksjSb2sA==
|
|
||||||
|
|
||||||
css-blank-pseudo@^0.1.4:
|
css-blank-pseudo@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5"
|
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5"
|
||||||
@ -3207,6 +3212,13 @@ duplexify@^3.4.2, duplexify@^3.6.0:
|
|||||||
readable-stream "^2.0.0"
|
readable-stream "^2.0.0"
|
||||||
stream-shift "^1.0.0"
|
stream-shift "^1.0.0"
|
||||||
|
|
||||||
|
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.322, electron-to-chromium@^1.3.488:
|
electron-to-chromium@^1.3.322, electron-to-chromium@^1.3.488:
|
||||||
version "1.3.496"
|
version "1.3.496"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.496.tgz#3f43d32930481d82ad3663d79658e7c59a58af0b"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.496.tgz#3f43d32930481d82ad3663d79658e7c59a58af0b"
|
||||||
@ -4790,6 +4802,22 @@ json5@^2.1.0, json5@^2.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.5"
|
minimist "^1.2.5"
|
||||||
|
|
||||||
|
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:
|
jsx-ast-utils@^2.4.1:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e"
|
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e"
|
||||||
@ -4798,6 +4826,23 @@ jsx-ast-utils@^2.4.1:
|
|||||||
array-includes "^3.1.1"
|
array-includes "^3.1.1"
|
||||||
object.assign "^4.1.0"
|
object.assign "^4.1.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"
|
||||||
|
|
||||||
kind-of@^2.0.1:
|
kind-of@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5"
|
||||||
@ -4979,6 +5024,36 @@ lodash._reinterpolate@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||||
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
|
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
|
||||||
|
|
||||||
|
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.memoize@^4.1.2:
|
lodash.memoize@^4.1.2:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||||
@ -4989,6 +5064,11 @@ lodash.merge@^4.6.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
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.template@^4.5.0:
|
lodash.template@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
|
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
|
||||||
|
Loading…
Reference in New Issue
Block a user