(user)(auth) don't use mastodon id as a placeholder email; alert if placeholder used as an email

This commit is contained in:
Andrea Vos 2024-11-09 19:41:01 +01:00
parent a346a82a32
commit 76ce55f1ed
7 changed files with 98 additions and 6 deletions

View File

@ -48,7 +48,7 @@
<template #general>
<div class="card mb-3">
<div class="card-body d-flex flex-column flex-md-row">
<div class="mx-2 text-center">
<div class="mx-2 text-center" style="min-width: 200px">
<p v-if="$isGranted('panel') || $isGranted('users') || $isGranted('community')">
<nuxt-link to="/admin" class="badge bg-primary text-white">
<Icon v="collective-logo.svg" class="inverted" />
@ -126,6 +126,8 @@
<hr>
<Alert v-if="$user() && $user()!.email.endsWith('.oauth')" type="warning" message="user.emailMissing" />
<form :inert="savingEmail" @submit.prevent="changeEmail">
<h3 class="h6">
<T>user.account.changeEmail.header</T>

View File

@ -685,6 +685,10 @@ user:
Under one account you can have one card per language, and those cards are linked through the common @username.
But you can might also want to set up multiple independent accounts, for example one for a work email footer,
and a separate one for close friends. If you add them here, you'll be able to quickly switch between those accounts.
emailMissing: >
Your account was created with a method that didn't share a verified email address with us.
Please change the placeholder below to your email and confirm it with a code
this way you'll have a fallback login method in case you lose access to the one you used.
profile:
description: 'Description'

View File

@ -888,6 +888,10 @@ user:
Under one account you can have one card per language, and those cards are linked through the common @username.
But you can might also want to set up multiple independent accounts, for example one for a work email footer,
and a separate one for close friends. If you add them here, you'll be able to quickly switch between those accounts.
emailMissing: >
Your account was created with a method that didn't share a verified email address with us.
Please change the placeholder below to your email and confirm it with a code
this way you'll have a fallback login method in case you lose access to the one you used.
profile:
description: 'Description'

View File

@ -1532,6 +1532,10 @@ user:
W ramach jednego konta możesz stworzyć po jednej wizytówce na język, będą one wtedy ze sobą związane poprzez wspólną @nazwę_użytkownicza.
Ale możesz też chcieć założyć niezależne konta, na przykład jedno do stopki służbowego maila,
a drugie dla bliskich znajomych. Jeśli dodasz je tutaj, będzie można szybko się między tymi kontami przełączać.
emailMissing: >
Twoje konto zostało utworzone z użyciem metody logowania, która nie udostępniła nam zweryfikowanego adresu email.
Aby mieć dostępny zapasowy sposób logowania, gdybyś straciłx dostęp do obecnego,
wpisz swój adres email poniżej i potwierdź go kodem.
profile:
description: 'Opis'

View File

@ -0,0 +1,75 @@
import './setup.ts';
import readline from 'readline';
import dbConnection from './db.ts';
import { normaliseExternalId } from '~/server/express/user.ts';
// TODO remove export after deployment migration
const fix = async (): Promise<void> => {
const db = await dbConnection();
const data = (await db.all<{ id: string; email: string; type: string; payload: string }>(`
SELECT u.id, u.email, a.type, a.payload
FROM users u
LEFT JOIN authenticators a on a.userId = u.id
WHERE a.type in ('mastodon', 'indieauth')
`));
const changes = {} as Record<string, string>;
// @ts-ignore
for (const { id, email, type, payload } of data) {
let payloadId;
try {
payloadId = JSON.parse(payload).id;
} catch (e) {
console.error(e);
continue;
}
if (type === 'mastodon') {
if (email === payloadId) {
changes[id] = `${normaliseExternalId(payloadId)}@mastodon.oauth`;
console.log(id, email, '->', changes[id]);
} else {
console.log(id, email, '(unchanged)');
}
} else if (type === 'indieauth') {
if (email === `indieauth@${normaliseExternalId(payloadId)}`) {
changes[id] = `${normaliseExternalId(payloadId)}@indieauth.oauth`;
console.log(id, email, '->', changes[id]);
} else {
console.log(id, email, '(unchanged)');
}
}
}
console.log(changes);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Do you want to persist the changes? (y/n): ', (answer) => {
if (answer.toLowerCase() === 'y') {
console.log('Proceeding...');
for (const [id, email] of Object.entries(changes)) {
db.get(`
UPDATE users
SET email='${email}'
WHERE id='${id}'
`);
}
} else {
console.log('Operation cancelled.');
process.exit(0);
}
rl.close();
});
};
fix();

View File

@ -704,6 +704,10 @@ router.get('/user/social-redirect/:provider/:locale', handleErrorAsync(async (re
return res.redirect(`/api/connect/${req.params.provider}?${new URLSearchParams(searchParams)}`);
}));
// TODO remove export after deployment migration
export const normaliseExternalId = (id: string): string => id.replace(/@/g, '_').replace(/^https?:\/\//, '')
.replace(new RegExp('/', 'g'), '_');
// happens on home
router.get('/user/social/:provider', handleErrorAsync(async (req, res) => {
if (!req.session.grant || !req.session.grant.response ||
@ -735,7 +739,7 @@ router.get('/user/social/:provider', handleErrorAsync(async (req, res) => {
: req.user;
const dbUser = await fetchOrCreateUser(req.db, user || {
email: payload.email || `${payload.id}@${req.params.provider}.oauth`,
email: payload.email || `${normaliseExternalId(payload.id)}@${req.params.provider}.oauth`,
name: payload.name,
}, req.params.provider);

View File

@ -108,7 +108,7 @@ if (enableApple) {
interface SocialProfile {
id: string;
email: string;
email: string | null;
name: string;
username?: string;
avatar?: string;
@ -168,8 +168,7 @@ export const handlers: Record<string, (r: GrantResponse) => SocialProfile> = {
const acct = `${r.profile.username}@${instance}`;
return {
id: acct,
// very possibly not really operated by the user
email: acct,
email: null,
name: acct,
avatar: r.profile.avatar,
access_token: r.access_token,
@ -179,7 +178,7 @@ export const handlers: Record<string, (r: GrantResponse) => SocialProfile> = {
indieauth(r: GrantResponse): SocialProfile {
return {
id: r.profile.me,
email: `indieauth@${r.profile.domain}`,
email: null,
name: r.profile.domain,
instance: (r as { instance: string }).instance,
};