Merge branch 'feature/auth-module'

This commit is contained in:
Hubert Van De Walle 2020-04-23 00:11:44 +02:00
commit 5560298388
17 changed files with 164 additions and 193 deletions

View File

@ -2,8 +2,10 @@ package be.vandewalleh.routing
import be.vandewalleh.auth.SimpleJWT import be.vandewalleh.auth.SimpleJWT
import be.vandewalleh.auth.UsernamePasswordCredential import be.vandewalleh.auth.UsernamePasswordCredential
import be.vandewalleh.extensions.respondStatus
import be.vandewalleh.services.UserService import be.vandewalleh.services.UserService
import io.ktor.application.* import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.request.* import io.ktor.request.*
import io.ktor.response.* import io.ktor.response.*
@ -18,20 +20,27 @@ fun Routing.login(kodein: Kodein) {
data class TokenResponse(val token: String) data class TokenResponse(val token: String)
route("/user/login"){ post("/user/login") {
post { val credential = call.receive<UsernamePasswordCredential>()
val credential = call.receive<UsernamePasswordCredential>()
val (email, password) = userService.getEmailAndPasswordFromUsername(credential.username) val (email, password) = userService.getEmailAndPasswordFromUsername(credential.username)
?: return@post call.respond(HttpStatusCode.Unauthorized) ?: return@post call.respond(HttpStatusCode.Unauthorized)
if (!BCrypt.checkpw(credential.password, password)) { if (!BCrypt.checkpw(credential.password, password)) {
return@post call.respond(HttpStatusCode.Unauthorized) return@post call.respond(HttpStatusCode.Unauthorized)
} }
return@post call.respond(TokenResponse(simpleJwt.sign(email))) return@post call.respond(TokenResponse(simpleJwt.sign(email)))
}
authenticate {
get("/user/me") {
// retrieve email from token
val email = call.principal<UserIdPrincipal>()!!.name
val info = userService.getUserInfo(email)
if (info != null) call.respond(mapOf("user" to info))
else call.respondStatus(HttpStatusCode.Unauthorized)
} }
} }
} }

View File

@ -48,6 +48,15 @@ class UserService(override val kodein: Kodein) : KodeinAware {
.firstOrNull() != null .firstOrNull() != null
} }
fun getUserInfo(email: String): UserInfoDto? {
return db.from(Users)
.select(Users.email, Users.username)
.where { Users.email eq email }
.limit(0, 1)
.map { UserInfoDto(it[Users.username]!!, it[Users.email]!!) }
.firstOrNull()
}
/** /**
* create a new user * create a new user
* password should already be hashed * password should already be hashed
@ -85,4 +94,5 @@ class UserService(override val kodein: Kodein) : KodeinAware {
} }
} }
data class UserDto(val username: String, val email: String, val password: String) data class UserDto(val username: String, val email: String, val password: String)
data class UserInfoDto(val username: String, val email: String)

View File

@ -1,28 +0,0 @@
import axios from 'axios'
import {mapState} from "vuex";
const state = mapState(['token'])
const apiClient = axios.create({
baseURL: `http://localhost:5000`,
withCredentials: false,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
axios.interceptors.request.use(
config => {
const token = state.token;
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
},
error => {
Promise.reject(error)
});
export default apiClient

View File

@ -7,7 +7,7 @@
lazy-validation lazy-validation
> >
<v-text-field <v-text-field
v-model="username" v-model="form.username"
:rules="usernameRules" :rules="usernameRules"
label="Username" label="Username"
required required
@ -15,7 +15,7 @@
></v-text-field> ></v-text-field>
<v-text-field <v-text-field
v-model="password" v-model="form.password"
:rules="passwordRules" :rules="passwordRules"
label="Password" label="Password"
required required
@ -30,7 +30,7 @@
<v-btn <v-btn
:disabled="!valid" :disabled="!valid"
color="success" color="success"
@click="submit" @click="userLogin"
> >
Login Login
</v-btn> </v-btn>
@ -42,17 +42,24 @@
export default { export default {
name: "LoginForm", name: "LoginForm",
methods: { methods: {
submit() { async userLogin() {
try {
const response = await this.$auth.loginWith('local', {data: this.form})
} catch (err) {
console.log(err)
}
} }
}, },
data: () => ({ data: () => ({
valid: false, valid: false,
username: '', form: {
username: '',
password: ''
},
usernameRules: [ usernameRules: [
v => !!v || 'Name is required', v => !!v || 'Name is required',
], ],
password: '',
passwordRules: [ passwordRules: [
v => !!v || 'Password is required', v => !!v || 'Password is required',
] ]

View File

@ -7,7 +7,7 @@
lazy-validation lazy-validation
> >
<v-text-field <v-text-field
v-model="username" v-model="form.username"
:rules="usernameRules" :rules="usernameRules"
label="Username" label="Username"
required required
@ -15,7 +15,7 @@
></v-text-field> ></v-text-field>
<v-text-field <v-text-field
v-model="email" v-model="form.email"
:rules="emailRules" :rules="emailRules"
label="Email" label="Email"
required required
@ -23,7 +23,7 @@
></v-text-field> ></v-text-field>
<v-text-field <v-text-field
v-model="password" v-model="form.password"
:rules="passwordRules" :rules="passwordRules"
label="Password" label="Password"
required required
@ -48,7 +48,7 @@
<v-btn <v-btn
:disabled="!valid" :disabled="!valid"
color="success" color="success"
@click="submit" @click="registerUser"
> >
Register Register
</v-btn> </v-btn>
@ -60,22 +60,25 @@
export default { export default {
name: "RegisterForm", name: "RegisterForm",
methods: { methods: {
submit() { async registerUser() {
const data = await this.$axios.post('/user', this.form)
console.log(data)
} }
}, },
data: () => ({ data: () => ({
valid: false, valid: false,
username: '', form: {
username: '',
email: '',
password: ''
},
usernameRules: [ usernameRules: [
v => !!v || 'Name is required', v => !!v || 'Name is required',
], ],
email: '',
emailRules: [ emailRules: [
v => !!v || 'Email is required', v => !!v || 'Email is required',
v => /.+@.+/.test(v) || 'E-mail must be valid', v => /.+@.+/.test(v) || 'E-mail must be valid',
], ],
password: '',
passwordRules: [ passwordRules: [
v => !!v || 'Password is required', v => !!v || 'Password is required',
v => v.length >= 6 || 'Password must be longer than 6 characters', v => v.length >= 6 || 'Password must be longer than 6 characters',

View File

@ -6,10 +6,11 @@
color="primary" color="primary"
dark dark
> >
<v-toolbar-title>Notes</v-toolbar-title> <v-btn to="/" text rounded>Simple Notes</v-btn>
<v-spacer/> <v-spacer/>
<v-btn to="/notes" text rounded>My notes</v-btn> <v-btn to="/notes" text rounded>My notes</v-btn>
<v-btn to="/account" text rounded>Account</v-btn> <v-btn outlined v-if="this.$store.state.auth.loggedIn">Welcome {{this.$store.state.auth.user.username}}</v-btn>
<v-btn v-else to="/account" text rounded>Account</v-btn>
</v-app-bar> </v-app-bar>
<v-content> <v-content>
<v-container> <v-container>
@ -20,9 +21,7 @@
</template> </template>
<script> <script>
export default { export default {}
}
</script> </script>
<style> <style>

View File

@ -1,33 +0,0 @@
<template>
<v-app>
<v-app-bar
fixed
app
color="primary"
dark
prominent
>
<v-toolbar-title>Notes</v-toolbar-title>
<v-spacer/>
<v-btn to="/notes" text rounded>My notes</v-btn>
<v-btn to="/account" text rounded>Account</v-btn>
</v-app-bar>
<v-content>
<v-container>
<nuxt/>
</v-container>
</v-content>
</v-app>
</template>
<script>
export default {
}
</script>
<style>
html {
overflow-y: auto
}
</style>

View File

@ -28,7 +28,7 @@ export default {
/* /*
** Plugins to load before mounting the App ** Plugins to load before mounting the App
*/ */
plugins: [], plugins: ['~/plugins/axios'],
/* /*
** Nuxt.js dev-modules ** Nuxt.js dev-modules
*/ */
@ -43,13 +43,38 @@ export default {
// Doc: https://axios.nuxtjs.org/usage // Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios', '@nuxtjs/axios',
// Doc: https://github.com/nuxt-community/dotenv-module // Doc: https://github.com/nuxt-community/dotenv-module
'@nuxtjs/dotenv' '@nuxtjs/dotenv',
'@nuxtjs/auth'
], ],
/* /*
** Axios module configuration ** Axios module configuration
** See https://axios.nuxtjs.org/options ** See https://axios.nuxtjs.org/options
*/ */
axios: {}, axios: {},
auth: {
redirect: {
login: '/account',
logout: '/',
home: '/',
},
watchLoggedIn: true,
//cookie: true,
strategies: {
local: {
endpoints: {
login: {url: '/user/login', method: 'post', propertyName: 'token'},
user: {url: '/user/me', method: 'get', propertyName: 'user'},
},
autoFetchUser: true
}
}
},
router: {
middleware: ['auth']
},
/* /*
** vuetify module configuration ** vuetify module configuration
** https://github.com/nuxt-community/vuetify-module ** https://github.com/nuxt-community/vuetify-module

View File

@ -8,10 +8,10 @@
"dev": "nuxt", "dev": "nuxt",
"build": "nuxt build", "build": "nuxt build",
"start": "nuxt start", "start": "nuxt start",
"generate": "nuxt generate", "generate": "nuxt generate"
"lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
}, },
"dependencies": { "dependencies": {
"@nuxtjs/auth": "^4.9.1",
"@nuxtjs/axios": "^5.3.6", "@nuxtjs/axios": "^5.3.6",
"@nuxtjs/dotenv": "^1.4.0", "@nuxtjs/dotenv": "^1.4.0",
"masonry-layout": "^4.2.2", "masonry-layout": "^4.2.2",

View File

@ -44,6 +44,9 @@
export default { export default {
name: "centered", name: "centered",
layout: "centered", layout: "centered",
options: {
auth: 'guest',
},
data: () => ({ data: () => ({
tab: 0, tab: 0,
tabs: ["Login", "Register"] tabs: ["Login", "Register"]

View File

@ -6,10 +6,10 @@
export default { export default {
title: 'Home', title: 'Home',
layout: 'home', options: {
data: () => ({ auth: false
},
}), data: () => ({}),
head: () => ({ head: () => ({
title: "Home" title: "Home"
}) })

View File

@ -0,0 +1,7 @@
export default function ({ $axios }) {
$axios.onRequest(config => {
console.log('Making request to ' + config.url)
})
$axios.setBaseURL('http://localhost:8081')
}

View File

@ -1,13 +0,0 @@
export function setToken(token) {
if (process.browser && token) {
localStorage.setItem('token', token)
}
}
export function clearToken() {
if (process.browser) {
localStorage.removeItem('token')
}
}
export default {setToken, clearToken}

View File

@ -1,19 +0,0 @@
import apiClient from '@/api'
export default {
async login({username, password}) {
try {
const {data} = await apiClient.post('/user/signin', {
username,
password
})
return {token: data["access_token"]}
} catch (e) {
if (e.response && e.response.status === 401)
return Promise.reject({invalid: true})
else
return Promise.reject({error: true})
}
}
}

View File

@ -1,20 +0,0 @@
import apiClient from '@/api'
export default {
async register({username, email, password}) {
try {
await apiClient.post('/user', {
username,
email,
password
})
return {success: true}
} catch (e) {
if (e.response && e.response.status === 409)
return Promise.reject({exists: true})
else
return Promise.reject({error: true})
}
}
}

View File

@ -1,27 +1,7 @@
import {clearToken, setToken} from "@/services/LocalStorageService"; export const state = () => ({})
export const state = () => ({ export const mutations = {}
token: ''
})
export const mutations = {
setToken(state, token) {
state.token = token
setToken(token)
},
clearToken(state) {
state.token = null
clearToken()
},
initToken(state) {
state.token = localStorage.getItem('token')
}
}
export const actions = {} export const actions = {}
export const getters = { export const getters = {}
isLoggedIn(state) {
return state.token !== null && state.token !== ''
}
}

View File

@ -1048,6 +1048,20 @@
webpack-node-externals "^1.7.2" webpack-node-externals "^1.7.2"
webpackbar "^4.0.0" webpackbar "^4.0.0"
"@nuxtjs/auth@^4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@nuxtjs/auth/-/auth-4.9.1.tgz#8827e4d23bf901711423434ad4a7073a8fc51603"
integrity sha512-h5VZanq2+P47jq3t0EnsZv800cg/ufOPC6JqvcyeDFJM99p58jHSODAjDuePo3PrZxd8hovMk7zusU5lOHgjvQ==
dependencies:
"@nuxtjs/axios" "^5.9.5"
body-parser "^1.19.0"
consola "^2.11.3"
cookie "^0.4.0"
is-https "^1.0.0"
js-cookie "^2.2.1"
lodash "^4.17.15"
nanoid "^2.1.11"
"@nuxtjs/axios@^5.3.6": "@nuxtjs/axios@^5.3.6":
version "5.9.7" version "5.9.7"
resolved "https://registry.yarnpkg.com/@nuxtjs/axios/-/axios-5.9.7.tgz#ec78b72dbcb70fceee7724b7f24e0cb4d924440c" resolved "https://registry.yarnpkg.com/@nuxtjs/axios/-/axios-5.9.7.tgz#ec78b72dbcb70fceee7724b7f24e0cb4d924440c"
@ -1059,6 +1073,17 @@
consola "^2.11.3" consola "^2.11.3"
defu "^1.0.0" defu "^1.0.0"
"@nuxtjs/axios@^5.9.5":
version "5.10.0"
resolved "https://registry.yarnpkg.com/@nuxtjs/axios/-/axios-5.10.0.tgz#3232980d781a208c672cd09e774e25e77208f0cb"
integrity sha512-6zAvjQ/37qMzyk0OmgFI2iLAOJ6ADdm29mfRlmOKR5iR1ip3Mxzhm02O8WLcET3UrE74WuIHdli/WK/5e35bXw==
dependencies:
"@nuxtjs/proxy" "^1.3.3"
axios "^0.19.2"
axios-retry "^3.1.6"
consola "^2.11.3"
defu "^2.0.2"
"@nuxtjs/dotenv@^1.4.0": "@nuxtjs/dotenv@^1.4.0":
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/@nuxtjs/dotenv/-/dotenv-1.4.1.tgz#dd5abb98e22cc7ae27139d3aa606151034293128" resolved "https://registry.yarnpkg.com/@nuxtjs/dotenv/-/dotenv-1.4.1.tgz#dd5abb98e22cc7ae27139d3aa606151034293128"
@ -1581,6 +1606,13 @@ axios-retry@^3.1.2:
dependencies: dependencies:
is-retry-allowed "^1.1.0" is-retry-allowed "^1.1.0"
axios-retry@^3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.1.6.tgz#566d591b4fbcbcf90728b639a7642eb5cc3785c9"
integrity sha512-pqOgBcpDtKU2YIBmHaHM8XnvzuOyRBxcvnD8+25uT0JcUEF0M1jq7Rpd7dTP27P8hQTynr/GNRuhEXZBLBffOw==
dependencies:
is-retry-allowed "^1.1.0"
axios@^0.19.2: axios@^0.19.2:
version "0.19.2" version "0.19.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
@ -1676,7 +1708,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
body-parser@1.19.0: body-parser@1.19.0, body-parser@^1.19.0:
version "1.19.0" version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
@ -2285,6 +2317,11 @@ cookie@^0.3.1:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
cookie@^0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
copy-concurrently@^1.0.0: copy-concurrently@^1.0.0:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
@ -2669,6 +2706,11 @@ defu@^1.0.0:
resolved "https://registry.yarnpkg.com/defu/-/defu-1.0.0.tgz#43acb09dfcf81866fa3b0fc047ece18e5c30df71" resolved "https://registry.yarnpkg.com/defu/-/defu-1.0.0.tgz#43acb09dfcf81866fa3b0fc047ece18e5c30df71"
integrity sha512-1Y1KRFxiiq+LYsZ3iP7xYSR8bHfmHFOUpDunZCN1ld1fGfDJWJIvkUBtjl3apnBwPuJtL/H7cwwlLYX8xPkraQ== integrity sha512-1Y1KRFxiiq+LYsZ3iP7xYSR8bHfmHFOUpDunZCN1ld1fGfDJWJIvkUBtjl3apnBwPuJtL/H7cwwlLYX8xPkraQ==
defu@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/defu/-/defu-2.0.2.tgz#9a3d4c1330d60c0ed4812e51864b948c51f7ad45"
integrity sha512-E5dO3ji0TmVcZaB/2G6Ovu5zNHbWkgCU7v+EoE/Jj1Lbwv1BB6hNNKLkio2ZLI3/e3avlO634QUhQl4iCpm3Bg==
depd@~1.1.2: depd@~1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
@ -3704,13 +3746,6 @@ ignore@^5.1.4:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==
imagesloaded@4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/imagesloaded/-/imagesloaded-4.1.4.tgz#1376efcd162bb768c34c3727ac89cc04051f3cc7"
integrity sha512-ltiBVcYpc/TYTF5nolkMNsnREHW+ICvfQ3Yla2Sgr71YFwQ86bDwV9hgpFhFtrGPuwEx5+LqOHIrdXBdoWwwsA==
dependencies:
ev-emitter "^1.0.0"
import-cwd@^2.0.0: import-cwd@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
@ -3951,6 +3986,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
dependencies: dependencies:
is-extglob "^2.1.1" is-extglob "^2.1.1"
is-https@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-https/-/is-https-1.0.0.tgz#9c1dde000dc7e7288edb983bef379e498e7cb1bf"
integrity sha512-1adLLwZT9XEXjzhQhZxd75uxf0l+xI9uTSFaZeSESjL3E1eXSPpO+u5RcgqtzeZ1KCaNvtEwZSTO2P4U5erVqQ==
is-nan@^1.2.1: is-nan@^1.2.1:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03"
@ -4068,6 +4108,11 @@ jest-worker@^25.1.0:
merge-stream "^2.0.0" merge-stream "^2.0.0"
supports-color "^7.0.0" supports-color "^7.0.0"
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -4581,6 +4626,11 @@ nan@^2.12.1:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
nanoid@^2.1.11:
version "2.1.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
nanomatch@^1.2.9: nanomatch@^1.2.9:
version "1.2.13" version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@ -7093,15 +7143,6 @@ vue-loader@^15.9.1:
vue-hot-reload-api "^2.3.0" vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0" vue-style-loader "^4.1.0"
vue-masonry@^0.11.8:
version "0.11.8"
resolved "https://registry.yarnpkg.com/vue-masonry/-/vue-masonry-0.11.8.tgz#fc2dd458d13b557eebc68d70506af76aa6d1428e"
integrity sha512-O+T+3zUghbKpjc+5aubXr8Kg1h9P334+Or9euYyXsQYa3mtScUqZFI6A16BijR9v4hYdtKksuPzU0mQplUvhDA==
dependencies:
imagesloaded "4.1.4"
masonry-layout "^4.2.2"
vue "^2.0.0"
vue-meta@^2.3.3: vue-meta@^2.3.3:
version "2.3.3" version "2.3.3"
resolved "https://registry.yarnpkg.com/vue-meta/-/vue-meta-2.3.3.tgz#2a097f62817204b0da78be4d62aee0cb566eaee0" resolved "https://registry.yarnpkg.com/vue-meta/-/vue-meta-2.3.3.tgz#2a097f62817204b0da78be4d62aee0cb566eaee0"
@ -7154,7 +7195,7 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue@^2.0.0, vue@^2.6.11: vue@^2.6.11:
version "2.6.11" version "2.6.11"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5" resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ== integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==