mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 03:06:31 -05:00
temp commit for libravatar.ts
Signed-off-by: David Mehren <git@herrmehren.de>
This commit is contained in:
parent
170977baa9
commit
e321a8c740
1 changed files with 128 additions and 0 deletions
128
backend/src/users/photo/libravatar.ts
Normal file
128
backend/src/users/photo/libravatar.ts
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import dns from 'node:dns';
|
||||||
|
import { URL } from 'url';
|
||||||
|
|
||||||
|
export enum LibravatarFallbackType {
|
||||||
|
NOT_FOUND = '404',
|
||||||
|
SILHOUETTE = 'mm',
|
||||||
|
IDENTICON = 'identicon',
|
||||||
|
MONSTER_ID = 'monsterid',
|
||||||
|
WAVATAR = 'wavatar',
|
||||||
|
RETRO = 'retro',
|
||||||
|
ROBO_HASH = 'robohash',
|
||||||
|
PAGAN = 'pagan',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an avatar URL for a given email address by using federated Libravatar.
|
||||||
|
* @param email The email address of the user.
|
||||||
|
* @param size The size of the image in pixels.
|
||||||
|
* @param defaultFallback The type of fallback image to use when no image is found for the user.
|
||||||
|
* @return The URL of the avatar image for the user
|
||||||
|
*/
|
||||||
|
export async function generateAvatarUrlFromEmail(
|
||||||
|
email: string,
|
||||||
|
size = 96,
|
||||||
|
defaultFallback: LibravatarFallbackType = LibravatarFallbackType.IDENTICON,
|
||||||
|
): Promise<string> {
|
||||||
|
const emailParts = email.split('@');
|
||||||
|
if (emailParts.length !== 2) {
|
||||||
|
throw new Error('Invalid email address provided');
|
||||||
|
}
|
||||||
|
const avatarServer = await lookupAvatarServer(emailParts[1]);
|
||||||
|
const emailHash = crypto.createHash('md5').update(email).digest('hex');
|
||||||
|
return createAvatarUrl(avatarServer, emailHash, size, defaultFallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateAvatarUrlFromOpenid(
|
||||||
|
openid: string,
|
||||||
|
size = 96,
|
||||||
|
defaultFallback: LibravatarFallbackType = LibravatarFallbackType.IDENTICON,
|
||||||
|
): Promise<string> {
|
||||||
|
const openidUrl = new URL(openid);
|
||||||
|
const hash = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(openidUrl.toString())
|
||||||
|
.digest('hex');
|
||||||
|
const avatarServer = await lookupAvatarServer(openidUrl.hostname);
|
||||||
|
return createAvatarUrl(avatarServer, hash, size, defaultFallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAvatarUrl(
|
||||||
|
serverURL: URL,
|
||||||
|
hash: string,
|
||||||
|
size: number,
|
||||||
|
defaultFallback: LibravatarFallbackType,
|
||||||
|
): string {
|
||||||
|
serverURL.pathname = `/avatar/${hash}?s=${size}&d=${defaultFallback}`;
|
||||||
|
return serverURL.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function lookupAvatarServer(userDomain: string): Promise<URL> {
|
||||||
|
return (
|
||||||
|
(await lookupAvatarServerFromDns(userDomain, true)) ??
|
||||||
|
(await lookupAvatarServerFromDns(userDomain, false)) ??
|
||||||
|
new URL('https://seccdn.libravatar.org')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function lookupAvatarServerFromDns(
|
||||||
|
domain: string,
|
||||||
|
secure: boolean,
|
||||||
|
): Promise<URL | null> {
|
||||||
|
const srvType = secure ? '_avatars-sec._tcp' : '_avatars._tcp';
|
||||||
|
const srvRecord = await lookupSrv(srvType, domain);
|
||||||
|
if (!srvRecord) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const hostName = srvRecord.name;
|
||||||
|
const port = srvRecord.port;
|
||||||
|
const protocol = secure ? 'https:' : 'http:';
|
||||||
|
const url = new URL('https://example.com');
|
||||||
|
url.host = hostName;
|
||||||
|
url.port = `${port}`;
|
||||||
|
url.protocol = protocol;
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function lookupSrv(
|
||||||
|
srvType: string,
|
||||||
|
domain: string,
|
||||||
|
): Promise<dns.SrvRecord | null> {
|
||||||
|
const resolved = await dns.promises.resolveSrv(`${srvType}.${domain}`);
|
||||||
|
if (resolved.length < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const recordsByPriority: Map<number, dns.SrvRecord[]> = new Map(
|
||||||
|
[
|
||||||
|
...resolved.reduce((records, currentRecord) => {
|
||||||
|
records.set(
|
||||||
|
currentRecord.priority,
|
||||||
|
(records.get(currentRecord.priority) ?? []).concat(currentRecord),
|
||||||
|
);
|
||||||
|
return records;
|
||||||
|
}, new Map<number, dns.SrvRecord[]>()),
|
||||||
|
].sort(),
|
||||||
|
);
|
||||||
|
for (const priority of recordsByPriority.keys()) {
|
||||||
|
const recordsByWeight =
|
||||||
|
recordsByPriority
|
||||||
|
.get(priority)
|
||||||
|
?.sort((recordA, recordB) => recordB.weight - recordA.weight) ?? [];
|
||||||
|
for (const record of recordsByWeight) {
|
||||||
|
if (record.name.trim() !== '' && isValidSrvPort(record.port)) {
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidSrvPort(port: number): boolean {
|
||||||
|
return port > 1 && port < 65535;
|
||||||
|
}
|
Loading…
Reference in a new issue