#74 social login - admin panel

This commit is contained in:
Andrea Vos 2020-11-02 21:45:45 +01:00
parent 248f597f46
commit 508a27dcf7
7 changed files with 77 additions and 52 deletions

View File

@ -28,9 +28,16 @@
{{s.el.username}}
</td>
<td>
<a :href="`mailto:${s.el.email}`" target="_blank" rel="noopener">
{{s.el.email}}
</a>
<p>
<a :href="`mailto:${s.el.email}`" target="_blank" rel="noopener">
{{s.el.email}}
</a>
</p>
<ul v-if="s.el.socialConnections.length" class="list-inline">
<li v-for="conn in s.el.socialConnections" class="list-inline-item">
<Icon :v="socialProviders[conn].icon || conn" set="b"/>
</li>
</ul>
</td>
<td>
<span :class="['badge', s.el.roles === 'admin' ? 'badge-primary' : 'badge-light']">
@ -53,8 +60,12 @@
<script>
import {head} from "../src/helpers";
import {socialProviders} from "../src/data";
export default {
data() {
return { socialProviders }
},
async asyncData({ app, store }) {
if (!store.state.user || store.state.user.roles !== 'admin') {
return {};

View File

@ -1,15 +1,13 @@
import {gravatar} from "../src/helpers";
import {gravatar, now} from "../src/helpers";
import SQL from "sql-template-strings";
const now = Math.floor(Date.now() / 1000);
export default async (db, user) => {
if (user.avatarSource) {
const auth = await db.get(SQL`
SELECT payload FROM authenticators
WHERE type = ${user.avatarSource}
AND userId = ${user.id}
AND (validUntil IS NULL OR validUntil > ${now})
AND (validUntil IS NULL OR validUntil > ${now()})
`)
if (auth) {
return JSON.parse(auth.payload).avatar;

View File

@ -24,7 +24,7 @@ app.use(async function (req, res, next) {
next();
});
router.use(grant.express()(require('./social').default));
router.use(grant.express()(require('./social').config));
app.use(require('./routes/banner').default);

View File

@ -1,6 +1,8 @@
import { Router } from 'express';
import SQL from 'sql-template-strings';
import avatar from '../avatar';
import {config as socialLoginConfig} from "../social";
import {now} from "../../src/helpers";
const router = Router();
@ -16,6 +18,12 @@ router.get('/admin/users', async (req, res) => {
ORDER BY u.id DESC
`);
const authenticators = await req.db.all(SQL`
SELECT userId, type FROM authenticators
WHERE type IN (`.append(Object.keys(socialLoginConfig).map(k => `'${k}'`).join(',')).append(SQL`)
AND (validUntil IS NULL OR validUntil > ${now()})
`));
const groupedUsers = {};
for (let user of users) {
if (groupedUsers[user.id] === undefined) {
@ -24,12 +32,17 @@ router.get('/admin/users', async (req, res) => {
locale: undefined,
profiles: user.locale ? [user.locale] : [],
avatar: await avatar(req.db, user),
socialConnections: [],
}
} else {
groupedUsers[user.id].profiles.push(user.locale);
}
}
for (let auth of authenticators) {
groupedUsers[auth.userId].socialConnections.push(auth.type);
}
return res.json(groupedUsers);
});

View File

@ -1,14 +1,13 @@
import { Router } from 'express';
import SQL from 'sql-template-strings';
import {ulid} from "ulid";
import {buildDict, makeId} from "../../src/helpers";
import {buildDict, makeId, now} from "../../src/helpers";
import translations from "../translations";
import jwt from "../../src/jwt";
import mailer from "../../src/mailer";
import config from '../config';
import avatar from '../avatar';
const now = Math.floor(Date.now() / 1000);
import { config as socialLoginConfig, handlers as socialLoginHandlers } from '../social';
const USERNAME_CHARS = 'A-Za-zĄĆĘŁŃÓŚŻŹąćęłńóśżź0-9._-';
@ -21,7 +20,7 @@ const saveAuthenticator = async (db, type, user, payload, validForMinutes = null
${user ? user.id : null},
${type},
${JSON.stringify(payload)},
${validForMinutes ? (now + validForMinutes * 60) : null}
${validForMinutes ? (now() + validForMinutes * 60) : null}
)`);
return id;
}
@ -30,7 +29,7 @@ const findAuthenticator = async (db, id, type) => {
const authenticator = await db.get(SQL`SELECT * FROM authenticators
WHERE id = ${id}
AND type = ${type}
AND (validUntil IS NULL OR validUntil > ${now})
AND (validUntil IS NULL OR validUntil > ${now()})
`);
if (authenticator) {
@ -42,7 +41,7 @@ const findAuthenticator = async (db, id, type) => {
const invalidateAuthenticator = async (db, id) => {
await db.get(SQL`UPDATE authenticators
SET validUntil = ${now}
SET validUntil = ${now()}
WHERE id = ${id}
`);
}
@ -246,39 +245,6 @@ router.post('/user/delete', async (req, res) => {
return res.json(true);
});
const socialLoginHandlers = {
twitter(r) {
return {
id: r.profile.id_str,
email: r.profile.email,
name: r.profile.screen_name,
avatar: r.profile.profile_image_url_https.replace('_normal', '_400x400'),
access_token: r.access_token,
access_secret: r.access_secret,
}
},
facebook(r) {
return {
id: r.profile.id,
email: r.profile.email,
name: r.profile.name,
avatar: r.profile.picture.data.url,
access_token: r.access_token,
access_secret: r.access_secret,
}
},
google(r) {
return {
id: r.profile.sub,
email: r.profile.email_verified !== false ? r.profile.email : undefined,
name: r.profile.email,
avatar: r.profile.picture,
access_token: r.access_token,
access_secret: r.access_secret,
}
},
};
router.get('/user/social/:provider', async (req, res) => {
const payload = socialLoginHandlers[req.params.provider](req.session.grant.response)
@ -286,7 +252,7 @@ router.get('/user/social/:provider', async (req, res) => {
SELECT * FROM authenticators
WHERE type = ${req.params.provider}
AND payload LIKE ${'{"id":"' + payload.id + '"%'}
AND (validUntil IS NULL OR validUntil > ${now})
AND (validUntil IS NULL OR validUntil > ${now()})
`)
const user = auth ? await req.db.get(SQL`
@ -319,9 +285,9 @@ router.get('/user/social-connections', async (req, res) => {
const authenticators = await req.db.all(SQL`
SELECT type, payload FROM authenticators
WHERE type IN (`.append(Object.keys(socialLoginHandlers).map(k => `'${k}'`).join(',')).append(SQL`)
WHERE type IN (`.append(Object.keys(socialLoginConfig).map(k => `'${k}'`).join(',')).append(SQL`)
AND userId = ${req.user.id}
AND (validUntil IS NULL OR validUntil > ${now})
AND (validUntil IS NULL OR validUntil > ${now()})
`));
return res.json(buildDict(function* () {
@ -340,7 +306,7 @@ router.post('/user/social-connection/:provider/disconnect', async (req, res) =>
SELECT id FROM authenticators
WHERE type = ${req.params.provider}
AND userId = ${req.user.id}
AND (validUntil IS NULL OR validUntil > ${now})
AND (validUntil IS NULL OR validUntil > ${now()})
`)
await invalidateAuthenticator(req.db, auth.id)

View File

@ -1,4 +1,4 @@
export default {
export const config = {
defaults: {
origin: process.env.BASE_URL,
transport: 'session',
@ -25,3 +25,36 @@ export default {
callback: '/api/user/social/google',
}
}
export const handlers = {
twitter(r) {
return {
id: r.profile.id_str,
email: r.profile.email,
name: r.profile.screen_name,
avatar: r.profile.profile_image_url_https.replace('_normal', '_400x400'),
access_token: r.access_token,
access_secret: r.access_secret,
}
},
facebook(r) {
return {
id: r.profile.id,
email: r.profile.email,
name: r.profile.name,
avatar: r.profile.picture.data.url,
access_token: r.access_token,
access_secret: r.access_secret,
}
},
google(r) {
return {
id: r.profile.sub,
email: r.profile.email_verified !== false ? r.profile.email : undefined,
name: r.profile.email,
avatar: r.profile.picture,
access_token: r.access_token,
access_secret: r.access_secret,
}
},
};

View File

@ -118,3 +118,7 @@ export const curry = function (func) {
export const capitalise = function (word) {
return word.substring(0, 1).toUpperCase() + word.substring(1);
}
export const now = function () {
return Math.floor(Date.now() / 1000);
}