+ Add CSRF protection for every requestpull/169/head
@@ -13,5 +13,6 @@ | |||||
</noscript> | </noscript> | ||||
<div id="app"></div> | <div id="app"></div> | ||||
<!-- built files will be auto injected --> | <!-- built files will be auto injected --> | ||||
<link rel="stylesheet" href="https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css"> | |||||
</body> | </body> | ||||
</html> | </html> |
@@ -27,16 +27,15 @@ | |||||
Create | Create | ||||
</a> | </a> | ||||
<div class="navbar-dropdown"> | <div class="navbar-dropdown"> | ||||
<router-link | |||||
to="/" | |||||
<a | |||||
@click="createPin" | |||||
class="navbar-item"> | class="navbar-item"> | ||||
Pin | Pin | ||||
</router-link> | |||||
<router-link | |||||
to="/" | |||||
</a> | |||||
<a | |||||
class="navbar-item"> | class="navbar-item"> | ||||
Board | Board | ||||
</router-link> | |||||
</a> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div | <div | ||||
@@ -104,8 +103,7 @@ | |||||
<script> | <script> | ||||
import api from './api'; | import api from './api'; | ||||
import LoginForm from './LoginForm.vue'; | |||||
import SignUpForm from './SignUpForm.vue'; | |||||
import modals from './modals'; | |||||
export default { | export default { | ||||
name: 'p-header', | name: 'p-header', | ||||
@@ -128,6 +126,8 @@ export default { | |||||
onSignUpSucceed() { | onSignUpSucceed() { | ||||
this.initializeUser(true); | this.initializeUser(true); | ||||
}, | }, | ||||
onPinCreated() { | |||||
}, | |||||
logOut() { | logOut() { | ||||
api.User.logOut().then( | api.User.logOut().then( | ||||
() => { | () => { | ||||
@@ -136,24 +136,13 @@ export default { | |||||
); | ); | ||||
}, | }, | ||||
logIn() { | 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() { | signUp() { | ||||
this.$buefy.modal.open({ | |||||
parent: this, | |||||
component: SignUpForm, | |||||
hasModalCard: true, | |||||
events: { | |||||
'signup.succeed': this.onSignUpSucceed, | |||||
}, | |||||
}); | |||||
modals.openSignUp(this, this.onSignUpSucceed); | |||||
}, | }, | ||||
initializeUser(force = false) { | initializeUser(force = false) { | ||||
const self = this; | const self = this; | ||||
@@ -3,6 +3,17 @@ import storage from './utils/storage'; | |||||
const API_PREFIX = '/api/v2/'; | const API_PREFIX = '/api/v2/'; | ||||
const Pin = { | |||||
createFromURL(jsonData) { | |||||
const url = `${API_PREFIX}pins/`; | |||||
return axios.post( | |||||
url, | |||||
jsonData, | |||||
); | |||||
}, | |||||
}; | |||||
function fetchPins(offset, tagFilter, userFilter) { | function fetchPins(offset, tagFilter, userFilter) { | ||||
const url = `${API_PREFIX}pins/`; | const url = `${API_PREFIX}pins/`; | ||||
const queryArgs = { | const queryArgs = { | ||||
@@ -160,6 +171,7 @@ const User = { | |||||
}; | }; | ||||
export default { | export default { | ||||
Pin, | |||||
fetchPin, | fetchPin, | ||||
fetchPins, | fetchPins, | ||||
fetchPinsForBoard, | 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' | ROOT_URLCONF = 'pinry.urls' | ||||
MIDDLEWARE_CLASSES = [ | MIDDLEWARE_CLASSES = [ | ||||
'django.middleware.csrf.CsrfViewMiddleware', | |||||
'django.middleware.security.SecurityMiddleware', | 'django.middleware.security.SecurityMiddleware', | ||||
'django.contrib.sessions.middleware.SessionMiddleware', | 'django.contrib.sessions.middleware.SessionMiddleware', | ||||
'django.middleware.common.CommonMiddleware', | 'django.middleware.common.CommonMiddleware', | ||||
'django.contrib.auth.middleware.AuthenticationMiddleware', | 'django.contrib.auth.middleware.AuthenticationMiddleware', | ||||
'django.contrib.messages.middleware.MessageMiddleware', | 'django.contrib.messages.middleware.MessageMiddleware', | ||||
'django.middleware.clickjacking.XFrameOptionsMiddleware', | 'django.middleware.clickjacking.XFrameOptionsMiddleware', | ||||
'pinry.middleware.ForceCSRFCookieMiddleware', | |||||
'users.middleware.Public', | 'users.middleware.Public', | ||||
] | ] | ||||