mirror of
https://github.com/pinry/pinry.git
synced 2025-11-13 16:45:41 +01:00
Feature: Add pin-creation from remote-url
+ Add CSRF protection for every request
This commit is contained in:
committed by
Isaac Bythewood
parent
5282ed5e53
commit
bb24ed3ecf
@@ -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,
|
||||
|
||||
45
pinry-spa/src/components/modals.js
Normal file
45
pinry-spa/src/components/modals.js
Normal file
@@ -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,
|
||||
};
|
||||
63
pinry-spa/src/components/pin_create/FileUpload.vue
Normal file
63
pinry-spa/src/components/pin_create/FileUpload.vue
Normal file
@@ -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>
|
||||
114
pinry-spa/src/components/pin_create/PinCreateModal.vue
Normal file
114
pinry-spa/src/components/pin_create/PinCreateModal.vue
Normal file
@@ -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>
|
||||
17
pinry-spa/src/views/PinCreate.vue
Normal file
17
pinry-spa/src/views/PinCreate.vue
Normal file
@@ -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>
|
||||
11
pinry/middleware.py
Normal file
11
pinry/middleware.py
Normal file
@@ -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',
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user