#74 social login
This commit is contained in:
parent
9d94cf84e0
commit
fa9bdee3ee
11
.env.dist
11
.env.dist
|
@ -1,8 +1,19 @@
|
|||
BASE_URL=http://localhost:3000
|
||||
|
||||
SECRET=
|
||||
|
||||
MAILER_HOST=
|
||||
MAILER_PORT=
|
||||
MAILER_USER=
|
||||
MAILER_PASS=
|
||||
MAILER_FROM=
|
||||
MAILER_ADMINS=
|
||||
|
||||
TWITTER_KEY=
|
||||
TWITTER_SECRET=
|
||||
|
||||
FACEBOOK_KEY=
|
||||
FACEBOOK_SECRET=
|
||||
|
||||
GOOGLE_KEY=
|
||||
GOOGLE_SECRET=
|
||||
|
|
|
@ -67,14 +67,27 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Loading :value="profiles"><template v-if="profiles !== undefined">
|
||||
<h3 class="h4"><T>profile.list</T>:</h3>
|
||||
<ul class="list-group mb-3">
|
||||
<Loading :value="socialConnections">
|
||||
<template v-slot:header>
|
||||
<h3 class="h4"><T>user.socialConnection.list</T>:</h3>
|
||||
</template>
|
||||
<ul v-if="socialConnections !== undefined" class="list-group">
|
||||
<li v-for="(providerOptions, provider) in socialProviders" :key="provider" :class="['list-group-item', 'en' === config.locale ? 'profile-current' : '']">
|
||||
<SocialConnection :provider="provider" :providerOptions="providerOptions" :connection="socialConnections[provider]" @disconnected="socialConnections[provider] = undefined"/>
|
||||
</li>
|
||||
</ul>
|
||||
</Loading>
|
||||
|
||||
<Loading :value="profiles">
|
||||
<template v-slot:header>
|
||||
<h3 class="h4"><T>profile.list</T>:</h3>
|
||||
</template>
|
||||
<ul v-if="profiles !== undefined" class="list-group">
|
||||
<li v-for="(options, locale) in locales" :key="locale" :class="['list-group-item', locale === config.locale ? 'profile-current' : '']">
|
||||
<ProfileOverview :profile="profiles[locale]" :locale="locale" @update="setProfiles"/>
|
||||
</li>
|
||||
</ul>
|
||||
</template></Loading>
|
||||
</Loading>
|
||||
|
||||
<section>
|
||||
<a href="#" class="badge badge-light border" @click.prevent="logout">
|
||||
|
@ -91,6 +104,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {socialProviders} from "../src/data";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
@ -102,10 +117,14 @@
|
|||
code: '',
|
||||
|
||||
profiles: undefined,
|
||||
|
||||
socialProviders,
|
||||
socialConnections: undefined,
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.profiles = await this.$axios.$get(`/profile/get/${this.$user().username}`);
|
||||
this.socialConnections = await this.$axios.$get(`/user/social-connections`);
|
||||
},
|
||||
methods: {
|
||||
async changeUsername() {
|
||||
|
@ -113,7 +132,7 @@
|
|||
|
||||
const response = await this.$axios.$post(`/user/change-username`, {
|
||||
username: this.username,
|
||||
}, { headers: this.$auth() });
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
this.error = response.error;
|
||||
|
@ -130,7 +149,7 @@
|
|||
email: this.email,
|
||||
authId: this.changeEmailAuthId,
|
||||
code: this.code,
|
||||
}, { headers: this.$auth() });
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
this.error = response.error;
|
||||
|
@ -160,7 +179,7 @@
|
|||
async deleteAccount() {
|
||||
await this.$confirm(this.$t('user.deleteAccountConfirm'), 'danger');
|
||||
|
||||
const response = await this.$axios.$post(`/user/delete`, {}, { headers: this.$auth() });
|
||||
const response = await this.$axios.$post(`/user/delete`);
|
||||
|
||||
this.logout();
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<img :src="gravatar" alt="" class="rounded-circle" :style="`width: 100%;max-width: ${dsize};max-height: ${dsize};`"/>
|
||||
<img :src="src || gravatar" alt="" class="rounded-circle" :style="`width: 100%;max-width: ${dsize};max-height: ${dsize};`"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -10,6 +10,7 @@
|
|||
user: { required: true },
|
||||
size: { 'default': 128 },
|
||||
dsize: { 'default': '6rem' },
|
||||
src: {},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -182,7 +182,7 @@
|
|||
if (this.nounsRaw !== undefined) {
|
||||
return;
|
||||
}
|
||||
this.nounsRaw = await this.$axios.$get(`/nouns/all/${this.config.locale}`, { headers: this.$auth() });
|
||||
this.nounsRaw = await this.$axios.$get(`/nouns/all/${this.config.locale}`);
|
||||
},
|
||||
async setFilter(filter) {
|
||||
this.filter = filter;
|
||||
|
@ -200,7 +200,7 @@
|
|||
this.$refs.form.edit(noun);
|
||||
},
|
||||
async approve(noun) {
|
||||
await this.$axios.$post(`/nouns/approve/${noun.id}`, {}, { headers: this.$auth() });
|
||||
await this.$axios.$post(`/nouns/approve/${noun.id}`);
|
||||
if (noun.base) {
|
||||
delete this.nouns[noun.base];
|
||||
}
|
||||
|
@ -209,7 +209,7 @@
|
|||
this.$forceUpdate();
|
||||
},
|
||||
async hide(noun) {
|
||||
await this.$axios.$post(`/nouns/hide/${noun.id}`, {}, { headers: this.$auth() });
|
||||
await this.$axios.$post(`/nouns/hide/${noun.id}`);
|
||||
noun.approved = false;
|
||||
this.$forceUpdate();
|
||||
},
|
||||
|
@ -217,7 +217,7 @@
|
|||
if (!confirm('Czy na pewno usunąć ten wpis?')) {
|
||||
return false;
|
||||
}
|
||||
await this.$axios.$post(`/nouns/remove/${noun.id}`, {}, { headers: this.$auth() });
|
||||
await this.$axios.$post(`/nouns/remove/${noun.id}`);
|
||||
delete this.nouns[noun.id];
|
||||
this.$forceUpdate();
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<section class="w-100">
|
||||
<slot name="header"></slot>
|
||||
<div v-if="isLoaded">
|
||||
<slot/>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
<div v-if="token === null">
|
||||
<form @submit.prevent="login">
|
||||
<p>
|
||||
<Icon v="info-circle"/>
|
||||
<T>user.login.why</T>
|
||||
</p>
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" v-model="usernameOrEmail"
|
||||
:placeholder="$t('user.login.placeholder')" autofocus required/>
|
||||
|
@ -14,9 +18,12 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="small">
|
||||
<T>user.login.why</T>
|
||||
</p>
|
||||
<div class="btn-group btn-block mb-3">
|
||||
<a :href="`/api/connect/${provider}`" v-for="(providerOptions, provider) in socialProviders" class="btn btn-outline-primary">
|
||||
<Icon :v="providerOptions.icon || provider" set="b"/>
|
||||
{{ providerOptions.name }}
|
||||
</a>
|
||||
</div>
|
||||
<p class="small text-muted">
|
||||
<T>terms.consent</T>
|
||||
<nuxt-link :to="`/${config.user.termsRoute}`">
|
||||
|
@ -54,6 +61,7 @@
|
|||
|
||||
<script>
|
||||
import jwt from 'jsonwebtoken';
|
||||
import {socialProviders} from "../src/data";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
@ -63,6 +71,8 @@
|
|||
code: '',
|
||||
|
||||
error: '',
|
||||
|
||||
socialProviders,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -142,7 +142,7 @@
|
|||
methods: {
|
||||
async submit(event) {
|
||||
this.submitting = true;
|
||||
await this.$axios.$post(`/nouns/submit/${this.config.locale}`, this.form, { headers: this.$auth() });
|
||||
await this.$axios.$post(`/nouns/submit/${this.config.locale}`, this.form);
|
||||
|
||||
this.submitting = false;
|
||||
this.afterSubmit = true;
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
await this.$confirm(this.$t('profile.deleteConfirm'), 'danger');
|
||||
|
||||
this.deleting = true;
|
||||
const response = await this.$axios.$post(`/profile/delete/${locale}`, {}, { headers: this.$auth() });
|
||||
const response = await this.$axios.$post(`/profile/delete/${locale}`);
|
||||
this.deleting = false;
|
||||
this.$emit('update', response);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center">
|
||||
<span class="my-2">
|
||||
<Icon :v="providerOptions.icon || provider" set="b"/>
|
||||
{{ providerOptions.name }}
|
||||
</span>
|
||||
<span v-if="connection === undefined">
|
||||
<a :href="`/api/connect/${provider}`" class="badge badge-light border">
|
||||
<Icon v="link"/>
|
||||
<T>user.socialConnection.connect</T>
|
||||
</a>
|
||||
</span>
|
||||
<span v-else class="text-center">
|
||||
<span class="mr-3">
|
||||
<Avatar :src="connection.avatar" :user="$user()" dsize="2rem"/>
|
||||
{{connection.name}}
|
||||
</span>
|
||||
<br class="d-md-none"/>
|
||||
<a :href="`/api/connect/${provider}`" class="badge badge-light border">
|
||||
<Icon v="sync"/>
|
||||
<T>user.socialConnection.refresh</T>
|
||||
</a>
|
||||
<Spinner v-if="disconnecting"/>
|
||||
<a v-else href="#" class="badge badge-light" @click.prevent="disconnect">
|
||||
<Icon v="unlink"/>
|
||||
<T>user.socialConnection.disconnect</T>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
provider: { required: true },
|
||||
providerOptions: { required: true },
|
||||
connection: {},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
disconnecting: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async disconnect() {
|
||||
await this.$confirm(this.$t('user.socialConnection.disconnectConfirm', {email: this.$user().email}), 'danger');
|
||||
|
||||
this.disconnecting = true;
|
||||
const response = await this.$axios.$post(`/user/social-connection/${this.provider}/disconnect`);
|
||||
this.disconnecting = false;
|
||||
this.$emit('disconnected', response);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -100,7 +100,7 @@
|
|||
methods: {
|
||||
async submit() {
|
||||
this.submitting = true;
|
||||
await this.$axios.$post(`/sources/submit/${this.config.locale}`, this.form, { headers: this.$auth() });
|
||||
await this.$axios.$post(`/sources/submit/${this.config.locale}`, this.form);
|
||||
|
||||
this.submitting = false;
|
||||
this.afterSubmit = true;
|
||||
|
|
|
@ -684,7 +684,7 @@ user:
|
|||
|
||||
Jeśli nie zamawiałxś tego kodu, po prostu zignoruj tę wiadomość.
|
||||
why: >
|
||||
Założenie konta pozwala na zarządzanie swoimi wizytówkami ({/@andrea=przykład}).
|
||||
Założenie konta pozwala na zarządzanie swoimi wizytówkami ({/@andrea=takimi jak ta}).
|
||||
code:
|
||||
action: 'Sprawdź'
|
||||
invalid: 'Kod nieprawidłowy.'
|
||||
|
@ -706,6 +706,12 @@ user:
|
|||
change: 'Zmień'
|
||||
deleteAccount: 'Usuń konto'
|
||||
deleteAccountConfirm: 'Czy na pewno chcesz usunąć swoje konto? Ta operacja jest nieodwracalna!'
|
||||
socialConnection:
|
||||
list: 'Połączenia z social media'
|
||||
connect: 'Połącz'
|
||||
refresh: 'Odśwież'
|
||||
disconnect: 'Rozłącz'
|
||||
disconnectConfirm: 'Czy na pewno chcesz usunąć to połączenie? (Zawsze możesz logować się przez maila %email%)'
|
||||
|
||||
profile:
|
||||
description: 'Opis'
|
||||
|
|
|
@ -15,9 +15,12 @@
|
|||
"@nuxtjs/redirect-module": "^0.3.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"canvas": "^2.6.1",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"cookie-universal-nuxt": "^2.1.4",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.1",
|
||||
"grant": "^5.4.5",
|
||||
"js-base64": "^3.5.2",
|
||||
"js-md5": "^0.7.3",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
|
|
|
@ -11,11 +11,6 @@ export default ({app, store}) => {
|
|||
}
|
||||
|
||||
Vue.prototype.$user = _ => store.state.user;
|
||||
Vue.prototype.$auth = _ => {
|
||||
return store.state.token ? {
|
||||
authorization: 'Bearer ' + store.state.token,
|
||||
} : {};
|
||||
};
|
||||
Vue.prototype.$admin = _ => {
|
||||
return store.state.user && store.state.user.authenticated && store.state.user.roles === 'admin';
|
||||
};
|
||||
|
|
|
@ -175,7 +175,7 @@
|
|||
},
|
||||
async save() {
|
||||
this.saving = true;
|
||||
const response = await this.$axios.$post(`/profile/save/${this.config.locale}`, {
|
||||
await this.$axios.$post(`/profile/save/${this.config.locale}`, {
|
||||
names: listToDict(this.names),
|
||||
pronouns: listToDict(this.pronouns),
|
||||
description: this.description,
|
||||
|
@ -183,7 +183,7 @@
|
|||
links: [...this.links],
|
||||
flags: [...this.flags],
|
||||
words: this.words.map(x => listToDict(x)),
|
||||
}, { headers: this.$auth() });
|
||||
});
|
||||
this.saving = false;
|
||||
this.$router.push(`/@${this.$user().username}`)
|
||||
},
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
import express from 'express';
|
||||
import authenticate from '../src/authenticate';
|
||||
import dbConnection from './db';
|
||||
import session from 'express-session';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import grant from "grant";
|
||||
import router from "./routes/user";
|
||||
|
||||
const app = express()
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(cookieParser());
|
||||
app.use(session({
|
||||
secret: process.env.SECRET,
|
||||
cookie: {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
},
|
||||
}));
|
||||
|
||||
app.use(async function (req, res, next) {
|
||||
req.rawUser = authenticate(req);
|
||||
|
@ -13,7 +24,9 @@ app.use(async function (req, res, next) {
|
|||
req.admin = req.user && req.user.roles === 'admin';
|
||||
req.db = await dbConnection();
|
||||
next();
|
||||
})
|
||||
});
|
||||
|
||||
router.use(grant.express()(require('./social').default));
|
||||
|
||||
app.use(require('./routes/banner').default);
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Router } from 'express';
|
||||
import SQL from 'sql-template-strings';
|
||||
import {ulid} from "ulid";
|
||||
import {makeId} from "../../src/helpers";
|
||||
import {buildDict, makeId} from "../../src/helpers";
|
||||
import translations from "../translations";
|
||||
import jwt from "../../src/jwt";
|
||||
import mailer from "../../src/mailer";
|
||||
import config from '../config';
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
|
||||
|
@ -46,7 +47,7 @@ const invalidateAuthenticator = async (db, id) => {
|
|||
}
|
||||
|
||||
const defaultUsername = async (db, email) => {
|
||||
const base = email.substring(0, email.indexOf('@'))
|
||||
const base = email.substring(0, email.includes('@') ? email.indexOf('@') : email.length)
|
||||
.padEnd(4, '0')
|
||||
.substring(0, 12)
|
||||
.replace(new RegExp(`[^${USERNAME_CHARS}]`, 'g'), '_');
|
||||
|
@ -62,12 +63,12 @@ const defaultUsername = async (db, email) => {
|
|||
}
|
||||
}
|
||||
|
||||
const issueAuthentication = async (db, user) => {
|
||||
const fetchOrCreateUser = async (db, user) => {
|
||||
let dbUser = await db.get(SQL`SELECT * FROM users WHERE email = ${normalise(user.email)}`);
|
||||
if (!dbUser) {
|
||||
dbUser = {
|
||||
id: ulid(),
|
||||
username: await defaultUsername(db, user.email),
|
||||
username: await defaultUsername(db, user.name || user.email),
|
||||
email: normalise(user.email),
|
||||
roles: 'user',
|
||||
avatarSource: null,
|
||||
|
@ -76,12 +77,16 @@ const issueAuthentication = async (db, user) => {
|
|||
VALUES (${dbUser.id}, ${dbUser.username}, ${dbUser.email}, ${dbUser.roles}, ${dbUser.avatarSource})`)
|
||||
}
|
||||
|
||||
return {
|
||||
token: jwt.sign({
|
||||
...dbUser,
|
||||
authenticated: true,
|
||||
}),
|
||||
};
|
||||
return dbUser;
|
||||
}
|
||||
|
||||
const issueAuthentication = async (db, user) => {
|
||||
const dbUser = await fetchOrCreateUser(db, user);
|
||||
|
||||
return jwt.sign({
|
||||
...dbUser,
|
||||
authenticated: true,
|
||||
});
|
||||
}
|
||||
|
||||
const validateEmail = (email) => {
|
||||
|
@ -150,7 +155,7 @@ router.post('/user/validate', async (req, res) => {
|
|||
|
||||
await invalidateAuthenticator(req.db, authenticator);
|
||||
|
||||
return res.json(await issueAuthentication(req.db, req.rawUser));
|
||||
return res.json({token: await issueAuthentication(req.db, req.rawUser)});
|
||||
});
|
||||
|
||||
router.post('/user/change-username', async (req, res) => {
|
||||
|
@ -169,7 +174,7 @@ router.post('/user/change-username', async (req, res) => {
|
|||
|
||||
await req.db.get(SQL`UPDATE users SET username = ${req.body.username} WHERE email = ${normalise(req.user.email)}`);
|
||||
|
||||
return res.json(await issueAuthentication(req.db, req.user));
|
||||
return res.json({token: await issueAuthentication(req.db, req.user)});
|
||||
});
|
||||
|
||||
router.post('/user/change-email', async (req, res) => {
|
||||
|
@ -218,7 +223,7 @@ router.post('/user/change-email', async (req, res) => {
|
|||
await req.db.get(SQL`UPDATE users SET email = ${authenticator.payload.to} WHERE email = ${normalise(req.user.email)}`);
|
||||
req.user.email = authenticator.payload.to;
|
||||
|
||||
return res.json(await issueAuthentication(req.db, req.user));
|
||||
return res.json({token: await issueAuthentication(req.db, req.user)});
|
||||
});
|
||||
|
||||
router.post('/user/delete', async (req, res) => {
|
||||
|
@ -238,4 +243,108 @@ 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) {
|
||||
console.log(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) {
|
||||
console.log(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)
|
||||
|
||||
const auth = await req.db.get(SQL`
|
||||
SELECT * FROM authenticators
|
||||
WHERE type = ${req.params.provider}
|
||||
AND payload LIKE ${'{"id":"' + payload.id + '"%'}
|
||||
AND (validUntil IS NULL OR validUntil > ${now})
|
||||
`)
|
||||
|
||||
const user = auth ? await req.db.get(SQL`
|
||||
SELECT * FROM users
|
||||
WHERE id = ${auth.userId}
|
||||
`) : req.user;
|
||||
|
||||
const dbUser = await fetchOrCreateUser(req.db, user || {
|
||||
email: payload.email || `${payload.id}@${req.params.provider}.oauth`,
|
||||
name: payload.name,
|
||||
});
|
||||
|
||||
const token = jwt.sign({
|
||||
...dbUser,
|
||||
authenticated: true,
|
||||
});
|
||||
|
||||
if (auth) {
|
||||
await invalidateAuthenticator(req.db, auth.id);
|
||||
}
|
||||
await saveAuthenticator(req.db, req.params.provider, dbUser, payload);
|
||||
|
||||
return res.cookie('token', token).redirect('/' + config.user.route);
|
||||
});
|
||||
|
||||
router.get('/user/social-connections', async (req, res) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({error: 'Unauthorised'});
|
||||
}
|
||||
|
||||
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`)
|
||||
AND userId = ${req.user.id}
|
||||
AND (validUntil IS NULL OR validUntil > ${now})
|
||||
`));
|
||||
|
||||
return res.json(buildDict(function* () {
|
||||
for (let auth of authenticators) {
|
||||
yield [auth.type, JSON.parse(auth.payload)];
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
router.post('/user/social-connection/:provider/disconnect', async (req, res) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({error: 'Unauthorised'});
|
||||
}
|
||||
|
||||
const auth = await req.db.get(SQL`
|
||||
SELECT id FROM authenticators
|
||||
WHERE type = ${req.params.provider}
|
||||
AND userId = ${req.user.id}
|
||||
AND (validUntil IS NULL OR validUntil > ${now})
|
||||
`)
|
||||
|
||||
await invalidateAuthenticator(req.db, auth.id)
|
||||
|
||||
return res.json('ok');
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
export default {
|
||||
defaults: {
|
||||
origin: process.env.BASE_URL,
|
||||
transport: 'session',
|
||||
state: true,
|
||||
prefix: '/api/connect',
|
||||
scope: ['email'],
|
||||
response: ['tokens', 'raw', 'profile'],
|
||||
},
|
||||
twitter: {
|
||||
key: process.env.TWITTER_KEY,
|
||||
secret: process.env.TWITTER_SECRET,
|
||||
callback: '/api/user/social/twitter',
|
||||
profile_url: 'https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true',
|
||||
},
|
||||
facebook: {
|
||||
key: process.env.FACEBOOK_KEY,
|
||||
secret: process.env.FACEBOOK_SECRET,
|
||||
callback: '/api/user/social/facebook',
|
||||
profile_url: 'https://graph.facebook.com/me?fields=email,name,picture'
|
||||
},
|
||||
google: {
|
||||
key: process.env.GOOGLE_KEY,
|
||||
secret: process.env.GOOGLE_SECRET,
|
||||
callback: '/api/user/social/google',
|
||||
}
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
import jwt from './jwt';
|
||||
|
||||
export default ({headers: { authorization }}) => {
|
||||
if (!authorization || !authorization.startsWith('Bearer ')) {
|
||||
return null;
|
||||
export default ({cookies, headers}) => {
|
||||
if (headers.authorization && headers.authorization.startsWith('Bearer ')) {
|
||||
return jwt.validate(headers.authorization.substring(7));
|
||||
}
|
||||
|
||||
return jwt.validate(authorization.substring(7));
|
||||
if (cookies.token && cookies.token !== 'null') {
|
||||
return jwt.validate(cookies.token)
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,12 @@ export const locales = {
|
|||
// nl: { name: 'Nederlands', url: 'https://nl.pronouns.page' },
|
||||
}
|
||||
|
||||
export const socialProviders = {
|
||||
twitter: { name: 'Twitter' },
|
||||
facebook: { name: 'Facebook' },
|
||||
google: { name: 'Google' },
|
||||
}
|
||||
|
||||
import templatesRaw from '../data/templates/templates.tsv';
|
||||
export const templates = parseTemplates(templatesRaw);
|
||||
|
||||
|
|
126
yarn.lock
126
yarn.lock
|
@ -1891,6 +1891,16 @@ asn1.js@^4.0.0:
|
|||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
|
||||
asn1.js@^5.3.0:
|
||||
version "5.4.1"
|
||||
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
|
||||
integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
|
||||
dependencies:
|
||||
bn.js "^4.0.0"
|
||||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
asn1@~0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
|
@ -2828,11 +2838,24 @@ convert-source-map@^1.7.0:
|
|||
dependencies:
|
||||
safe-buffer "~5.1.1"
|
||||
|
||||
cookie-parser@^1.4.5:
|
||||
version "1.4.5"
|
||||
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.5.tgz#3e572d4b7c0c80f9c61daf604e4336831b5d1d49"
|
||||
integrity sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==
|
||||
dependencies:
|
||||
cookie "0.4.0"
|
||||
cookie-signature "1.0.6"
|
||||
|
||||
cookie-signature@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
|
||||
|
||||
cookie-signature@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.1.0.tgz#cc94974f91fb9a9c1bb485e95fc2b7f4b120aff2"
|
||||
integrity sha512-Alvs19Vgq07eunykd3Xy2jF0/qSNv2u7KDbAek9H5liV1UMijbqFs5cycZvv5dVsvseT/U4H8/7/w8Koh35C4A==
|
||||
|
||||
cookie-universal-nuxt@^2.1.4:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cookie-universal-nuxt/-/cookie-universal-nuxt-2.1.4.tgz#323f8645501f88cb2422127ad8ba2ee40187b716"
|
||||
|
@ -2859,7 +2882,7 @@ cookie@^0.3.1:
|
|||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
|
||||
|
||||
cookie@^0.4.0:
|
||||
cookie@^0.4.0, cookie@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
|
||||
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
|
||||
|
@ -3335,6 +3358,11 @@ depd@~1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
depd@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||
|
||||
des.js@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
|
||||
|
@ -3504,7 +3532,7 @@ electron-to-chromium@^1.3.488:
|
|||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.500.tgz#ac082dd2279251b14f1a7f6af6fd16655cf3eb7b"
|
||||
integrity sha512-Zz8BZh4Ssb/rZBaicqpi+GOQ0uu3y+24+MxBLCk0UYt8EGoZRP4cYzYHHwXGZfrSbCU4VDjbWN+Tg+TPgOUX6Q==
|
||||
|
||||
elliptic@^6.0.0, elliptic@^6.5.2:
|
||||
elliptic@^6.0.0, elliptic@^6.5.2, elliptic@^6.5.3:
|
||||
version "6.5.3"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
|
||||
integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
|
||||
|
@ -3740,6 +3768,20 @@ expand-brackets@^2.1.4:
|
|||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
express-session@^1.17.1:
|
||||
version "1.17.1"
|
||||
resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.1.tgz#36ecbc7034566d38c8509885c044d461c11bf357"
|
||||
integrity sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==
|
||||
dependencies:
|
||||
cookie "0.4.0"
|
||||
cookie-signature "1.0.6"
|
||||
debug "2.6.9"
|
||||
depd "~2.0.0"
|
||||
on-headers "~1.0.2"
|
||||
parseurl "~1.3.3"
|
||||
safe-buffer "5.2.0"
|
||||
uid-safe "~2.1.5"
|
||||
|
||||
express@^4.16.3, express@^4.17.1:
|
||||
version "4.17.1"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
||||
|
@ -4252,6 +4294,20 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
|
|||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||
|
||||
grant@^5.4.5:
|
||||
version "5.4.5"
|
||||
resolved "https://registry.yarnpkg.com/grant/-/grant-5.4.5.tgz#ab1d3001bf33392eb1c09266ff8bc91f6553cbe0"
|
||||
integrity sha512-w1/mbzGEztHSpZIH96K0O43lgascuvP/jyiXpu6JsLQ4Hu7c3ZKb7KqDTQhRQdmJ+oZoyDffPK2reuVn6eU9Kw==
|
||||
dependencies:
|
||||
qs "^6.9.4"
|
||||
request-compose "^2.1.0"
|
||||
request-oauth "^1.0.0"
|
||||
optionalDependencies:
|
||||
cookie "^0.4.1"
|
||||
cookie-signature "^1.1.0"
|
||||
jwk-to-pem "^2.0.3"
|
||||
jws "^4.0.0"
|
||||
|
||||
gzip-size@^5.0.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
|
||||
|
@ -5148,6 +5204,24 @@ jwa@^1.4.1:
|
|||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jwa@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
|
||||
integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jwk-to-pem@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/jwk-to-pem/-/jwk-to-pem-2.0.4.tgz#774e168697a0b52054e8cfb0bcd57e0f4c398e2d"
|
||||
integrity sha512-4CCK9UBHNWjWtfSHdyu3I6rA8vlN5cWqnVuwY0cOMyXtw6M1tP+yrM8GZpwk+P932Dc3cLag4d35B6CqyIf89A==
|
||||
dependencies:
|
||||
asn1.js "^5.3.0"
|
||||
elliptic "^6.5.3"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
|
@ -5156,6 +5230,14 @@ jws@^3.2.2:
|
|||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
|
||||
integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
|
||||
dependencies:
|
||||
jwa "^2.0.0"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||
|
@ -6012,7 +6094,7 @@ nuxt@^2.14.7:
|
|||
"@nuxt/telemetry" "^1.2.3"
|
||||
"@nuxt/webpack" "2.14.7"
|
||||
|
||||
oauth-sign@~0.9.0:
|
||||
oauth-sign@^0.9.0, oauth-sign@~0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
||||
|
@ -7220,6 +7302,11 @@ qs@6.7.0:
|
|||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
|
||||
qs@^6.9.3, qs@^6.9.4:
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687"
|
||||
integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==
|
||||
|
||||
qs@~6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
|
@ -7243,6 +7330,11 @@ querystring@0.2.0, querystring@^0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
|
||||
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
|
||||
|
||||
random-bytes@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
||||
integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=
|
||||
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||
|
@ -7443,6 +7535,20 @@ repeat-string@^1.6.1:
|
|||
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
||||
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
|
||||
|
||||
request-compose@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/request-compose/-/request-compose-2.1.0.tgz#2064e886a7745a6af425609aa116ebfeffc1792d"
|
||||
integrity sha512-mIWvU9HA2whb/fHcqeQ0LQXAImCGISqUPyjuFF2rALhym2Fu4ebZGv7wxFA78rsJO/fn2OeEaK54TSjwSwRAFw==
|
||||
|
||||
request-oauth@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/request-oauth/-/request-oauth-1.0.0.tgz#c8ebe047fb4ff4d5aa4ddb33e98b9ac74c625674"
|
||||
integrity sha512-wsDzIq1Qu2itLDlcpFph8xh5Q+FVyUj4os5zdQTlZL/JvZYF/qOyaawVPsxxhDG4QwCB3tzSFprj6dkjqR+e8w==
|
||||
dependencies:
|
||||
oauth-sign "^0.9.0"
|
||||
qs "^6.9.3"
|
||||
uuid "^3.4.0"
|
||||
|
||||
request@^2.87.0:
|
||||
version "2.88.2"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
|
||||
|
@ -7583,6 +7689,11 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
|||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-buffer@5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
|
@ -8482,6 +8593,13 @@ uglify-js@^3.5.1:
|
|||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.10.0.tgz#397a7e6e31ce820bfd1cb55b804ee140c587a9e7"
|
||||
integrity sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==
|
||||
|
||||
uid-safe@~2.1.5:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"
|
||||
integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==
|
||||
dependencies:
|
||||
random-bytes "~1.0.0"
|
||||
|
||||
ulid@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ulid/-/ulid-2.3.0.tgz#93063522771a9774121a84d126ecd3eb9804071f"
|
||||
|
@ -8680,7 +8798,7 @@ utils-merge@1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||
|
||||
uuid@^3.3.2:
|
||||
uuid@^3.3.2, uuid@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
|
Reference in New Issue