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>
|
</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
|
<a
|
||||||
to="/"
|
@click="createPin"
|
||||||
class="navbar-item">
|
class="navbar-item">
|
||||||
Pin
|
Pin
|
||||||
</router-link>
|
</a>
|
||||||
<router-link
|
<a
|
||||||
to="/"
|
|
||||||
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 modals from './modals';
|
||||||
import SignUpForm from './SignUpForm.vue';
|
|
||||||
|
|
||||||
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({
|
modals.openLogin(this, this.onLoginSucceed);
|
||||||
parent: this,
|
},
|
||||||
component: LoginForm,
|
createPin() {
|
||||||
hasModalCard: true,
|
modals.openPinCreate(this, this.onPinCreated);
|
||||||
events: {
|
|
||||||
'login.succeed': this.onLoginSucceed,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
signUp() {
|
signUp() {
|
||||||
this.$buefy.modal.open({
|
modals.openSignUp(this, this.onSignUpSucceed);
|
||||||
parent: this,
|
|
||||||
component: SignUpForm,
|
|
||||||
hasModalCard: true,
|
|
||||||
events: {
|
|
||||||
'signup.succeed': 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,
|
||||||
|
|||||||
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'
|
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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user