mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-22 12:03:25 -04:00
Merge branch 'gravatar-sha256-p' into 'main'
Gravatar sha256 See merge request PronounsPage/PronounsPage!605
This commit is contained in:
commit
ae94bd9631
@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computedAsync } from '@vueuse/core';
|
||||
import { useCookie, useFetch } from 'nuxt/app';
|
||||
|
||||
import useConfig from '../composables/useConfig.ts';
|
||||
@ -41,6 +42,10 @@ const username = ref(user.value.username);
|
||||
const email = ref(user.value.email);
|
||||
const socialLookup = ref(Boolean(user.value.socialLookup));
|
||||
|
||||
const gravatarSrc = computedAsync(async () => {
|
||||
return user.value ? await gravatar(user.value) : undefined;
|
||||
});
|
||||
|
||||
const message = ref('');
|
||||
const messageParams = ref({});
|
||||
const messageIcon = ref<string | null>(null);
|
||||
@ -319,7 +324,7 @@ const addBrackets = (str: string): string => {
|
||||
<div v-else class="mt-3">
|
||||
Gravatar:
|
||||
<a href="#" @click.prevent="setAvatar('gravatar')">
|
||||
<Avatar :user="user" :src="gravatar(user)" dsize="2rem" />
|
||||
<Avatar :user="user" :src="gravatarSrc" dsize="2rem" />
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="user.avatarSource">
|
||||
|
@ -1,7 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { computedAsync } from '@vueuse/core';
|
||||
|
||||
import { fallbackAvatar, gravatar } from '~/src/helpers.ts';
|
||||
import type { User } from '~/src/user.ts';
|
||||
|
||||
interface AvatarUser extends Pick<User, 'username' | 'emailHash'> {
|
||||
email?: User['email'];
|
||||
avatar?: string;
|
||||
avatarSource?: User['avatarSource'];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
user: AvatarUser;
|
||||
src?: string;
|
||||
size?: number;
|
||||
dsize?: string;
|
||||
validate?: boolean;
|
||||
}>(), {
|
||||
size: 128,
|
||||
dsize: '6rem',
|
||||
});
|
||||
|
||||
const failedToLoad = ref(false);
|
||||
|
||||
const resolvedSrc = computedAsync(async () => {
|
||||
return props.src ||
|
||||
props.user.avatar ||
|
||||
(props.user.avatarSource === 'gravatar' && props.user.email !== undefined
|
||||
? await gravatar(props.user as AvatarUser & Required<Pick<AvatarUser, 'email'>>, props.size)
|
||||
: fallbackAvatar(props.user, props.size));
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<img
|
||||
:src="src || user.avatar || (user.avatarSource === 'gravatar' ? gravatar(user, size) : fallbackAvatar(user, size))"
|
||||
:src="resolvedSrc"
|
||||
alt=""
|
||||
class="rounded-circle"
|
||||
:style="`width: ${dsize};height: ${dsize};`"
|
||||
@ -11,27 +45,6 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fallbackAvatar, gravatar } from '../src/helpers.ts';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
user: { required: true },
|
||||
src: {},
|
||||
size: { default: 128 },
|
||||
dsize: { default: '6rem' },
|
||||
validate: { type: Boolean },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fallbackAvatar,
|
||||
gravatar,
|
||||
failedToLoad: false,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.failed-to-load {
|
||||
max-width: 200px;
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import sorter from 'avris-sorter';
|
||||
import md5 from 'js-md5';
|
||||
import { useCookie } from 'nuxt/app';
|
||||
|
||||
import useConfig from '~/composables/useConfig.ts';
|
||||
@ -8,11 +7,11 @@ import useDark from '~/composables/useDark.ts';
|
||||
import useDialogue from '~/composables/useDialogue.ts';
|
||||
import { longtimeCookieSetting } from '~/src/cookieSettings.ts';
|
||||
import { LoadScriptError } from '~/src/errors.ts';
|
||||
import { executeUnlessPrerendering } from '~/src/helpers.ts';
|
||||
import { executeUnlessPrerendering, sha256 } from '~/src/helpers.ts';
|
||||
import { useMainStore } from '~/store/index.ts';
|
||||
|
||||
// no need to be super secure, just a sign that the page is not public
|
||||
const TESTER_PASSWORD_HASH = '82feeb96d60170e714df8fb062301e90';
|
||||
const TESTER_PASSWORD_HASH = '3754c03824c4ea3b13e7b4b2d7ad35992dbf64f9ee680493fc7093bf176f49e5';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -35,8 +34,12 @@ const requiresLogin = computed((): boolean => {
|
||||
return !config.macrolanguage?.enabled && (runtimeConfig.public.env === 'test' ||
|
||||
config.locale !== '_' && !locales[config.locale]?.published);
|
||||
});
|
||||
const testerPasswordValid = computed((): boolean => {
|
||||
return !!testerPasswordCookie.value && md5(testerPasswordCookie.value) === TESTER_PASSWORD_HASH;
|
||||
const testerPasswordValid = ref<boolean | undefined>();
|
||||
watchEffect(async () => {
|
||||
testerPasswordValid.value = undefined;
|
||||
if (testerPasswordCookie.value) {
|
||||
testerPasswordValid.value = await sha256(testerPasswordCookie.value) === TESTER_PASSWORD_HASH;
|
||||
}
|
||||
});
|
||||
const checkTesterPassword = (): void => {
|
||||
testerPasswordCookie.value = testerPassword.value;
|
||||
@ -123,7 +126,7 @@ const loadAds = async (): Promise<void> => {
|
||||
Sign in
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="testerPasswordCookie && !testerPasswordValid" class="small text-danger">
|
||||
<p v-if="testerPasswordCookie && testerPasswordValid === false" class="small text-danger">
|
||||
<Icon v="exclamation-triangle" />
|
||||
Password invalid
|
||||
</p>
|
||||
|
@ -34,7 +34,6 @@
|
||||
"ics": "^3.7.6",
|
||||
"jose": "^5.9.6",
|
||||
"js-base64": "^3.5.2",
|
||||
"js-md5": "^0.7.3",
|
||||
"jsdom": "^26.0.0",
|
||||
"luxon": "^1.28.1",
|
||||
"mastodon": "^1.2.2",
|
||||
|
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -62,9 +62,6 @@ importers:
|
||||
js-base64:
|
||||
specifier: ^3.5.2
|
||||
version: 3.7.7
|
||||
js-md5:
|
||||
specifier: ^0.7.3
|
||||
version: 0.7.3
|
||||
jsdom:
|
||||
specifier: ^26.0.0
|
||||
version: 26.0.0(canvas@3.1.0)
|
||||
@ -5521,9 +5518,6 @@ packages:
|
||||
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
js-md5@0.7.3:
|
||||
resolution: {integrity: sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@ -15118,8 +15112,6 @@ snapshots:
|
||||
|
||||
js-cookie@3.0.5: {}
|
||||
|
||||
js-md5@0.7.3: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
|
@ -17,7 +17,7 @@ export default async (
|
||||
if (user.avatarSource && process.env.NUXT_PUBLIC_CLOUDFRONT && user.avatarSource.startsWith(process.env.NUXT_PUBLIC_CLOUDFRONT!)) {
|
||||
return user.avatarSource;
|
||||
} else if (user.avatarSource === 'gravatar') {
|
||||
return gravatar(user);
|
||||
return await gravatar(user);
|
||||
} else if (user.avatarSource) {
|
||||
if (user.payload) {
|
||||
return fromPayload(user.payload);
|
||||
|
@ -64,7 +64,7 @@ const run = async (config: Config, baseUrl: string): Promise<void> => {
|
||||
const prev = fs.existsSync(prevPath) ? JSON.parse(fs.readFileSync(prevPath, 'utf-8')) : {};
|
||||
|
||||
const localEvents = (await import(`../locale/${config.locale}/calendar/events.ts`)).default;
|
||||
const current = buildCalendar(localEvents, process.env.NUXT_PUBLIC_BASE_URL!).buildSummary();
|
||||
const current = await buildCalendar(localEvents, process.env.NUXT_PUBLIC_BASE_URL!).buildSummary();
|
||||
const changedYears = new Set();
|
||||
for (const day of Object.keys(current)) {
|
||||
const year = day.substring(0, 4);
|
||||
|
@ -6,7 +6,6 @@ import * as Sentry from '@sentry/node';
|
||||
import { Router } from 'express';
|
||||
import type { Request, Response } from 'express';
|
||||
import { getH3Event } from 'h3-express';
|
||||
import md5 from 'js-md5';
|
||||
import type { ParsedQs } from 'qs';
|
||||
import SQL from 'sql-template-strings';
|
||||
import { ulid } from 'ulid';
|
||||
@ -19,6 +18,7 @@ import {
|
||||
now,
|
||||
isValidLink,
|
||||
newDate,
|
||||
sha256,
|
||||
} from '../../src/helpers.ts';
|
||||
import { normaliseUrl } from '../../src/links.ts';
|
||||
import type { Opinion } from '../../src/opinions.ts';
|
||||
@ -524,7 +524,7 @@ const fetchProfilesRoute = async (req: Request, res: Response, locale: string, u
|
||||
});
|
||||
}
|
||||
|
||||
user.emailHash = md5(user.email);
|
||||
user.emailHash = await sha256(user.email);
|
||||
delete user.email;
|
||||
user.avatar = await avatar(req.db, user);
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { S3 } from '@aws-sdk/client-s3';
|
||||
import { loadImage, createCanvas } from 'canvas';
|
||||
import md5 from 'js-md5';
|
||||
|
||||
import { newDate } from '../src/helpers.ts';
|
||||
|
||||
import { awsConfig, awsParams } from './aws.ts';
|
||||
import { awsConfig, awsParams } from '~/server/aws.ts';
|
||||
import { newDate, sha256 } from '~/src/helpers.ts';
|
||||
|
||||
const s3 = new S3(awsConfig);
|
||||
|
||||
@ -23,7 +21,7 @@ export default async (prefix: string, url: string, ttlDays: number | null = null
|
||||
|
||||
const isSvg = url.toLowerCase().replace(/\?.*$/, '').endsWith('.svg') || url.startsWith('data:image/svg+xml');
|
||||
|
||||
const key = `${prefix}/${md5(url)}.${isSvg ? 'svg' : 'png'}`;
|
||||
const key = `${prefix}/${await sha256(url)}.${isSvg ? 'svg' : 'png'}`;
|
||||
|
||||
try {
|
||||
const metadata = await s3.headObject({ Key: key, ...awsParams });
|
||||
|
@ -1,10 +1,8 @@
|
||||
import type { EventAttributes } from 'ics';
|
||||
import md5 from 'js-md5';
|
||||
import nepali from 'nepali-calendar-js';
|
||||
import { v5 as uuid5 } from 'uuid';
|
||||
|
||||
import { newDate } from '../helpers.ts';
|
||||
|
||||
import { newDate, sha256 } from '~/src/helpers.ts';
|
||||
import type { Translator } from '~/src/translator.ts';
|
||||
|
||||
export class Day {
|
||||
@ -448,16 +446,13 @@ export class Calendar {
|
||||
}
|
||||
}
|
||||
|
||||
buildSummary(): Record<string, string> {
|
||||
async buildSummary(): Promise<Record<string, string>> {
|
||||
const summary: Record<string, string> = {};
|
||||
for (const year of this.getAllYears()) {
|
||||
for (let month = 1; month <= 12; month++) {
|
||||
for (const day of iterateMonth(year.year, month)) {
|
||||
const events = [];
|
||||
for (const event of year.eventsByDate[day.toString()] || []) {
|
||||
events.push(event.name);
|
||||
}
|
||||
summary[day.toString()] = md5(JSON.stringify(events));
|
||||
const events = (year.eventsByDate[day.toString()] || []).map((event) => event.name);
|
||||
summary[day.toString()] = await sha256(JSON.stringify(events));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import * as Sentry from '@sentry/browser';
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
import { importSPKI, jwtVerify } from 'jose';
|
||||
import { Base64 } from 'js-base64';
|
||||
import md5 from 'js-md5';
|
||||
|
||||
import type { Database } from '../server/db.ts';
|
||||
|
||||
@ -148,8 +147,20 @@ export const fallbackAvatar = (user: Pick<User, 'username'>, size: number = 240)
|
||||
.replace(/\//g, '_')}.png`;
|
||||
};
|
||||
|
||||
export const gravatar = (user: Pick<User, 'username' | 'email'> & { emailHash?: string }, size: number = 240): string => {
|
||||
return `https://www.gravatar.com/avatar/${user.emailHash || md5(user.email)}?d=${encodeURIComponent(fallbackAvatar(user, size))}&s=${size}`;
|
||||
export const sha256 = async (message: string) => {
|
||||
const encoded = new TextEncoder().encode(message);
|
||||
const hashBuffer = await crypto.subtle.digest('sha-256', encoded);
|
||||
return Array.from(new Uint8Array(hashBuffer))
|
||||
.map((byte) => byte.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
};
|
||||
|
||||
export const gravatar = async (
|
||||
user: Pick<User, 'username' | 'email'> & { emailHash?: string },
|
||||
size: number = 240,
|
||||
): Promise<string> => {
|
||||
const emailHash = user.emailHash ?? await sha256(user.email);
|
||||
return `https://gravatar.com/avatar/${emailHash}?d=${encodeURIComponent(fallbackAvatar(user, size))}&s=${size}`;
|
||||
};
|
||||
|
||||
export interface DictEntry<K extends string | number | symbol, V> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user