mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-18 12:05:28 -04:00
#54 user accounts - admin panel
This commit is contained in:
parent
4937cc39da
commit
9fd711c04b
@ -1,5 +1,4 @@
|
||||
BASE_URL=http://localhost:3000
|
||||
SECRET=secret
|
||||
|
||||
MAILER_HOST=
|
||||
MAILER_PORT=
|
||||
|
@ -20,7 +20,7 @@
|
||||
<p>{{ email }}</p>
|
||||
</div>
|
||||
|
||||
<p v-if="$user().roles === 'admin'">
|
||||
<p v-if="$admin()">
|
||||
<span class="badge badge-primary"><T>user.account.admin</T></span>
|
||||
</p>
|
||||
|
||||
@ -45,9 +45,7 @@
|
||||
async changeUsername() {
|
||||
await this.post(`/user/change-username`, {
|
||||
username: this.username
|
||||
}, {
|
||||
headers: {...this.$auth()},
|
||||
});
|
||||
}, { headers: this.$auth() });
|
||||
},
|
||||
async post(url, data, options = {}) {
|
||||
this.error = '';
|
||||
|
@ -32,7 +32,7 @@
|
||||
<span class="d-none d-md-inline"><T>nouns.neuter</T></span>
|
||||
<span class="d-md-none"><T>nouns.neuterShort</T></span>
|
||||
</th>
|
||||
<th v-if="secret"></th>
|
||||
<th v-if="$admin()"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -118,9 +118,6 @@
|
||||
import { nounTemplates } from '../src/data';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
secret: {},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
@ -142,9 +139,9 @@
|
||||
methods: {
|
||||
async submit(event) {
|
||||
this.submitting = true;
|
||||
await this.$axios.$post(`/nouns/submit?secret=${this.secret}`, {
|
||||
await this.$axios.$post(`/nouns/submit`, {
|
||||
data: this.form,
|
||||
});
|
||||
}, { headers: this.$auth() });
|
||||
|
||||
this.submitting = false;
|
||||
this.afterSubmit = true;
|
||||
|
@ -86,7 +86,6 @@ export default {
|
||||
},
|
||||
env: {
|
||||
BASE_URL: process.env.BASE_URL,
|
||||
SECRET: process.env.SECRET,
|
||||
PUBLIC_KEY: fs.readFileSync(__dirname + '/keys/public.pem').toString(),
|
||||
},
|
||||
serverMiddleware: {
|
||||
|
@ -16,4 +16,7 @@ export default ({app, store}) => {
|
||||
authorization: 'Bearer ' + store.state.token,
|
||||
} : {};
|
||||
};
|
||||
Vue.prototype.$admin = _ => {
|
||||
return store.state.user && store.state.user.authenticated && store.state.user.roles === 'admin';
|
||||
};
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
<NounsExtra/>
|
||||
|
||||
<Loading :value="nounsRaw">
|
||||
<section v-if="secret">
|
||||
<section v-if="$admin()">
|
||||
<div class="alert alert-info">
|
||||
<strong>{{ nounsCountApproved() }}</strong> <T>nouns.approved</T>,
|
||||
<strong>{{ nounsCountPending() }}</strong> <T>nouns.pending</T>.
|
||||
@ -44,7 +44,7 @@
|
||||
</section>
|
||||
|
||||
<section class="table-responsive">
|
||||
<table :class="'table table-striped table-hover table-fixed-' + (secret ? 4 : 3)">
|
||||
<table :class="'table table-striped table-hover table-fixed-' + ($admin() ? 4 : 3)">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-nowrap">
|
||||
@ -59,7 +59,7 @@
|
||||
<Icon v="neuter"/>
|
||||
<T>nouns.neuter</T>
|
||||
</th>
|
||||
<th v-if="secret"></th>
|
||||
<th v-if="$admin()"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -83,7 +83,7 @@
|
||||
</ul>
|
||||
</small>
|
||||
|
||||
<button v-if="!secret" class="btn btn-outline-primary btn-sm m-1 hover-show" @click="edit(noun)">
|
||||
<button v-if="!$admin()" class="btn btn-outline-primary btn-sm m-1 hover-show" @click="edit(noun)">
|
||||
<Icon v="pen"/>
|
||||
<T>nouns.edit</T>
|
||||
</button>
|
||||
@ -128,7 +128,7 @@
|
||||
</ul>
|
||||
</small>
|
||||
</td>
|
||||
<td v-if="secret">
|
||||
<td v-if="$admin()">
|
||||
<ul class="list-unstyled">
|
||||
<li v-if="!noun.approved">
|
||||
<button class="btn btn-success btn-sm m-1" @click="approve(noun)">
|
||||
@ -160,7 +160,7 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<tr>
|
||||
<td :colspan="secret ? 4 : 3" class="text-center">
|
||||
<td :colspan="$admin() ? 4 : 3" class="text-center">
|
||||
<Icon v="search"/>
|
||||
<T>nouns.empty</T>
|
||||
</td>
|
||||
@ -173,7 +173,7 @@
|
||||
|
||||
<Separator icon="plus"/>
|
||||
|
||||
<NounSubmitForm ref="form" :secret="secret"/>
|
||||
<NounSubmitForm ref="form"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -189,12 +189,11 @@
|
||||
return {
|
||||
filter: '',
|
||||
nounsRaw: undefined,
|
||||
secret: this.$route.query.secret,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (process.client) {
|
||||
this.$axios.$get(`/nouns/all?secret=${this.$route.query.secret || ''}`).then(data => {
|
||||
this.$axios.$get(`/nouns/all`, { headers: this.$auth() }).then(data => {
|
||||
this.nounsRaw = data;
|
||||
});
|
||||
if (window.location.hash) {
|
||||
@ -221,7 +220,7 @@
|
||||
this.$refs.form.edit(noun);
|
||||
},
|
||||
async approve(noun) {
|
||||
await this.$axios.$post(`/nouns/approve/${noun.id}?secret=${this.secret || ''}`);
|
||||
await this.$axios.$post(`/nouns/approve/${noun.id}`, {}, { headers: this.$auth() });
|
||||
if (noun.base) {
|
||||
delete this.nouns[noun.base];
|
||||
}
|
||||
@ -230,7 +229,7 @@
|
||||
this.$forceUpdate();
|
||||
},
|
||||
async hide(noun) {
|
||||
await this.$axios.$post(`/nouns/hide/${noun.id}?secret=${this.secret || ''}`);
|
||||
await this.$axios.$post(`/nouns/hide/${noun.id}`, {}, { headers: this.$auth() });
|
||||
noun.approved = false;
|
||||
this.$forceUpdate();
|
||||
},
|
||||
@ -238,7 +237,7 @@
|
||||
if (!confirm('Czy na pewno usunąć ten wpis?')) {
|
||||
return false;
|
||||
}
|
||||
await this.$axios.$post(`/nouns/remove/${noun.id}?secret=${this.secret || ''}`);
|
||||
await this.$axios.$post(`/nouns/remove/${noun.id}`, {}, { headers: this.$auth() });
|
||||
delete this.nouns[noun.id];
|
||||
this.$forceUpdate();
|
||||
},
|
||||
|
9
server/authenticate.js
Normal file
9
server/authenticate.js
Normal file
@ -0,0 +1,9 @@
|
||||
import jwt from './jwt';
|
||||
|
||||
export default ({headers: { authorization }}) => {
|
||||
if (!authorization || !authorization.startsWith('Bearer ')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return jwt.validate(authorization.substring(7));
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
const dbConnection = require('./db');
|
||||
const SQL = require('sql-template-strings');
|
||||
import { ulid } from 'ulid'
|
||||
import authenticate from './authenticate';
|
||||
|
||||
const parseQuery = (queryString) => {
|
||||
const query = {};
|
||||
@ -64,20 +65,17 @@ const isTroll = (body) => {
|
||||
|
||||
export default async function (req, res, next) {
|
||||
const db = await dbConnection();
|
||||
|
||||
const [url, queryString] = req.url.split('?');
|
||||
const query = parseQuery(queryString || '');
|
||||
|
||||
const isAdmin = query['secret'] === process.env.SECRET;
|
||||
const user = authenticate(req);
|
||||
const isAdmin = user && user.authenticated && user.roles === 'admin';
|
||||
|
||||
let result = {error: 'Not found'}
|
||||
if (req.method === 'GET' && url === '/all') {
|
||||
if (req.method === 'GET' && req.url === '/all') {
|
||||
result = await db.all(`
|
||||
SELECT * FROM nouns
|
||||
${isAdmin ? '' : 'WHERE approved = 1'}
|
||||
ORDER BY approved, masc
|
||||
`);
|
||||
} else if (req.method === 'POST' && url === '/submit') {
|
||||
} else if (req.method === 'POST' && req.url === '/submit') {
|
||||
if (isAdmin || !isTroll(req.body.data)) {
|
||||
const id = ulid()
|
||||
await db.get(SQL`
|
||||
@ -94,14 +92,14 @@ export default async function (req, res, next) {
|
||||
}
|
||||
}
|
||||
result = 'ok';
|
||||
} else if (req.method === 'POST' && url.startsWith('/approve/') && isAdmin) {
|
||||
await approve(db, getId(url));
|
||||
} else if (req.method === 'POST' && req.url.startsWith('/approve/') && isAdmin) {
|
||||
await approve(db, getId(req.url));
|
||||
result = 'ok';
|
||||
} else if (req.method === 'POST' && url.startsWith('/hide/') && isAdmin) {
|
||||
await hide(db, getId(url));
|
||||
} else if (req.method === 'POST' && req.url.startsWith('/hide/') && isAdmin) {
|
||||
await hide(db, getId(req.url));
|
||||
result = 'ok';
|
||||
} else if (req.method === 'POST' && url.startsWith('/remove/') && isAdmin) {
|
||||
await remove(db, getId(url));
|
||||
} else if (req.method === 'POST' && req.url.startsWith('/remove/') && isAdmin) {
|
||||
await remove(db, getId(req.url));
|
||||
result = 'ok';
|
||||
}
|
||||
|
||||
|
@ -5,19 +5,12 @@ const SQL = require('sql-template-strings');
|
||||
import { ulid } from 'ulid';
|
||||
import translations from "./translations";
|
||||
const mailer = require('./mailer');
|
||||
import authenticate from './authenticate';
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
|
||||
const USERNAME_CHARS = 'A-Za-zĄĆĘŁŃÓŚŻŹąćęłńóśżź0-9._-';
|
||||
|
||||
const getUser = (authorization) => {
|
||||
if (!authorization || !authorization.startsWith('Bearer ')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return jwt.validate(authorization.substring(7));
|
||||
}
|
||||
|
||||
const saveAuthenticator = async (db, type, user, payload, validForMinutes = null) => {
|
||||
const id = ulid();
|
||||
await db.get(SQL`INSERT INTO authenticators (id, userId, type, payload, validUntil) VALUES (
|
||||
@ -109,7 +102,7 @@ const validate = async (db, user, code) => {
|
||||
|
||||
await invalidateAuthenticator(db, authenticator);
|
||||
|
||||
return await authenticate(db, user);
|
||||
return await issueAuthentication(db, user);
|
||||
}
|
||||
|
||||
const defaultUsername = async (db, email) => {
|
||||
@ -129,7 +122,7 @@ const defaultUsername = async (db, email) => {
|
||||
}
|
||||
}
|
||||
|
||||
const authenticate = async (db, user) => {
|
||||
const issueAuthentication = async (db, user) => {
|
||||
let dbUser = await db.get(SQL`SELECT * FROM users WHERE email = ${user.email}`);
|
||||
if (!dbUser) {
|
||||
dbUser = {
|
||||
@ -161,16 +154,15 @@ const changeUsername = async (db, user, username) => {
|
||||
|
||||
await db.get(SQL`UPDATE users SET username = ${username} WHERE email = ${user.email}`);
|
||||
|
||||
return await authenticate(db, user);
|
||||
return await issueAuthentication(db, user);
|
||||
}
|
||||
|
||||
export default async function (req, res, next) {
|
||||
const db = await dbConnection();
|
||||
const user = authenticate(req);
|
||||
|
||||
let result = {error: 'notfound'}
|
||||
|
||||
const user = getUser(req.headers.authorization);
|
||||
|
||||
if (req.method === 'POST' && req.url === '/init' && req.body.usernameOrEmail) {
|
||||
result = await init(db, req.body.usernameOrEmail)
|
||||
} else if (req.method === 'POST' && req.url === '/validate' && req.body.code) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user