+ Add CSRF protection for every requestpull/169/head
@@ -13,5 +13,6 @@ | |||
</noscript> | |||
<div id="app"></div> | |||
<!-- built files will be auto injected --> | |||
<link rel="stylesheet" href="https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css"> | |||
</body> | |||
</html> |
@@ -27,16 +27,15 @@ | |||
Create | |||
</a> | |||
<div class="navbar-dropdown"> | |||
<router-link | |||
to="/" | |||
<a | |||
@click="createPin" | |||
class="navbar-item"> | |||
Pin | |||
</router-link> | |||
<router-link | |||
to="/" | |||
</a> | |||
<a | |||
class="navbar-item"> | |||
Board | |||
</router-link> | |||
</a> | |||
</div> | |||
</div> | |||
<div | |||
@@ -104,8 +103,7 @@ | |||
<script> | |||
import api from './api'; | |||
import LoginForm from './LoginForm.vue'; | |||
import SignUpForm from './SignUpForm.vue'; | |||
import modals from './modals'; | |||
export default { | |||
name: 'p-header', | |||
@@ -128,6 +126,8 @@ export default { | |||
onSignUpSucceed() { | |||
this.initializeUser(true); | |||
}, | |||
onPinCreated() { | |||
}, | |||
logOut() { | |||
api.User.logOut().then( | |||
() => { | |||
@@ -136,24 +136,13 @@ export default { | |||
); | |||
}, | |||
logIn() { | |||
this.$buefy.modal.open({ | |||
parent: this, | |||
component: LoginForm, | |||
hasModalCard: true, | |||
events: { | |||
'login.succeed': this.onLoginSucceed, | |||
}, | |||
}); | |||
modals.openLogin(this, this.onLoginSucceed); | |||
}, | |||
createPin() { | |||
modals.openPinCreate(this, this.onPinCreated); | |||
}, | |||
signUp() { | |||
this.$buefy.modal.open({ | |||
parent: this, | |||
component: SignUpForm, | |||
hasModalCard: true, | |||
events: { | |||
'signup.succeed': this.onSignUpSucceed, | |||
}, | |||
}); | |||
modals.openSignUp(this, this.onSignUpSucceed); | |||
}, | |||
initializeUser(force = false) { | |||
const self = this; | |||
@@ -3,6 +3,17 @@ import storage from './utils/storage'; | |||
const API_PREFIX = '/api/v2/'; | |||
const Pin = { | |||
createFromURL(jsonData) { | |||
const url = `${API_PREFIX}pins/`; | |||
return axios.post( | |||
url, | |||
jsonData, | |||
); | |||
}, | |||
}; | |||
function fetchPins(offset, tagFilter, userFilter) { | |||
const url = `${API_PREFIX}pins/`; | |||
const queryArgs = { | |||
@@ -160,6 +171,7 @@ const User = { | |||
}; | |||
export default { | |||
Pin, | |||
fetchPin, | |||
fetchPins, | |||
fetchPinsForBoard, | |||
@@ -0,0 +1,45 @@ | |||
import PinCreateModal from './pin_create/PinCreateModal.vue'; | |||
import LoginForm from './LoginForm.vue'; | |||
import SignUpForm from './SignUpForm.vue'; | |||
function openPinCreate(vm, onCreate) { | |||
vm.$buefy.modal.open( | |||
{ | |||
parent: vm, | |||
component: PinCreateModal, | |||
hasModalCard: true, | |||
events: { | |||
'create.succeed': onCreate, | |||
}, | |||
}, | |||
); | |||
} | |||
function openLogin(vm, onSucceed) { | |||
vm.$buefy.modal.open({ | |||
parent: vm, | |||
component: LoginForm, | |||
hasModalCard: true, | |||
events: { | |||
'login.succeed': onSucceed, | |||
}, | |||
}); | |||
} | |||
function openSignUp(vm, onSignUpSucceed) { | |||
vm.$buefy.modal.open({ | |||
parent: vm, | |||
component: SignUpForm, | |||
hasModalCard: true, | |||
events: { | |||
'signup.succeed': onSignUpSucceed, | |||
}, | |||
}); | |||
} | |||
export default { | |||
openPinCreate, | |||
openLogin, | |||
openSignUp, | |||
}; |
@@ -0,0 +1,63 @@ | |||
<template> | |||
<div class="image-upload"> | |||
<div | |||
v-show="previewExists" | |||
class="has-text-centered is-center preview"> | |||
<img :src="previewImageURL"> | |||
</div> | |||
<div v-show="!previewExists"> | |||
<b-field> | |||
<b-upload v-model="dropFiles" | |||
multiple | |||
drag-drop> | |||
<section class="section"> | |||
<div class="content has-text-centered"> | |||
<p> | |||
<b-icon | |||
icon="upload" | |||
size="is-medium"> | |||
</b-icon> | |||
</p> | |||
<p>Drop your files here or click to upload</p> | |||
</div> | |||
</section> | |||
</b-upload> | |||
</b-field> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'FileUpload', | |||
props: { | |||
previewImageURL: { | |||
type: String, | |||
default: null, | |||
}, | |||
}, | |||
computed: { | |||
previewExists() { | |||
return this.previewImageURL !== null && this.previewImageURL !== ''; | |||
}, | |||
}, | |||
data() { | |||
return { | |||
dropFiles: [], | |||
}; | |||
}, | |||
methods: {}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../utils/pin'; | |||
@import '../utils/loader'; | |||
.preview > img { | |||
width: $pin-preview-width; | |||
height: auto; | |||
@include loader('../../assets/loader.gif'); | |||
} | |||
</style> |
@@ -0,0 +1,114 @@ | |||
<template> | |||
<div class="pin-create-modal"> | |||
<form action=""> | |||
<div class="modal-card" style="width: auto"> | |||
<header class="modal-card-head"> | |||
<p class="modal-card-title">New Pin</p> | |||
</header> | |||
<section class="modal-card-body"> | |||
<div class="columns"> | |||
<div class="column"> | |||
<FileUpload :previewImageURL="form.url.value"></FileUpload> | |||
</div> | |||
<div class="column"> | |||
<b-field label="Image URL" | |||
:type="form.url.type" | |||
:message="form.url.error"> | |||
<b-input | |||
type="text" | |||
v-model="form.url.value" | |||
placeholder="where to fetch the image" | |||
maxlength="256" | |||
> | |||
</b-input> | |||
</b-field> | |||
<b-field label="Image Referer" | |||
:type="form.referer.type" | |||
:message="form.referer.error"> | |||
<b-input | |||
type="text" | |||
v-model="form.referer.value" | |||
placeholder="where to find the pin" | |||
maxlength="256" | |||
> | |||
</b-input> | |||
</b-field> | |||
<b-field label="Descripton" | |||
:type="form.description.type" | |||
:message="form.description.error"> | |||
<b-input | |||
type="textarea" | |||
v-model="form.description.value" | |||
placeholder="idea from this pin" | |||
maxlength="1024" | |||
> | |||
</b-input> | |||
</b-field> | |||
<b-field label="Tags"> | |||
<b-taginput | |||
v-model="form.tags.value" | |||
ellipsis | |||
icon="label" | |||
placeholder="Add a tag"> | |||
</b-taginput> | |||
</b-field> | |||
</div> | |||
</div> | |||
</section> | |||
<footer class="modal-card-foot"> | |||
<button class="button" type="button" @click="$parent.close()">Close</button> | |||
<button | |||
@click="createPin" | |||
class="button is-primary">Create Pin | |||
</button> | |||
</footer> | |||
</div> | |||
</form> | |||
</div> | |||
</template> | |||
<script> | |||
import api from '../api'; | |||
import FileUpload from './FileUpload.vue'; | |||
function isURLBlank(url) { | |||
return url !== null && url !== ''; | |||
} | |||
export default { | |||
name: 'PinCreateModal', | |||
components: { | |||
FileUpload, | |||
}, | |||
data() { | |||
return { | |||
form: { | |||
url: { value: null, error: null, type: null }, | |||
referer: { value: null, error: null, type: null }, | |||
description: { value: null, error: null, type: null }, | |||
tags: { value: [], error: null, type: null }, | |||
}, | |||
}; | |||
}, | |||
methods: { | |||
createPin() { | |||
const self = this; | |||
if (isURLBlank(isURLBlank)) { | |||
const data = { | |||
url: this.form.url.value, | |||
referer: this.form.referer.value, | |||
description: this.form.description.value, | |||
tags: this.form.tags.value, | |||
}; | |||
const promise = api.Pin.createFromURL(data); | |||
promise.then( | |||
(resp) => { | |||
self.$emit('pin.created', resp); | |||
self.$parent.close(); | |||
}, | |||
); | |||
} | |||
}, | |||
}, | |||
}; | |||
</script> |
@@ -0,0 +1,17 @@ | |||
<template> | |||
<div class="pin-create"> | |||
<div></div> | |||
</div> | |||
</template> | |||
<script> | |||
// import PinCreateModal from './PinCreateModal.vue'; | |||
export default { | |||
name: 'PinCreate', | |||
}; | |||
</script> | |||
<style scoped> | |||
</style> |
@@ -0,0 +1,11 @@ | |||
from django.middleware.csrf import get_token | |||
class ForceCSRFCookieMiddleware: | |||
def process_request(self, request): | |||
if "CSRF_TOKEN" not in request.META: | |||
get_token(request) | |||
else: | |||
if request.method != "GET": | |||
get_token(request) | |||
return |
@@ -26,12 +26,15 @@ INSTALLED_APPS = [ | |||
ROOT_URLCONF = 'pinry.urls' | |||
MIDDLEWARE_CLASSES = [ | |||
'django.middleware.csrf.CsrfViewMiddleware', | |||
'django.middleware.security.SecurityMiddleware', | |||
'django.contrib.sessions.middleware.SessionMiddleware', | |||
'django.middleware.common.CommonMiddleware', | |||
'django.contrib.auth.middleware.AuthenticationMiddleware', | |||
'django.contrib.messages.middleware.MessageMiddleware', | |||
'django.middleware.clickjacking.XFrameOptionsMiddleware', | |||
'pinry.middleware.ForceCSRFCookieMiddleware', | |||
'users.middleware.Public', | |||
] | |||