Skip to content

Commit

Permalink
Merge pull request #167 from ensdomains/mdt/os-api-config
Browse files Browse the repository at this point in the history
add os api config, update csp, minor improvements
  • Loading branch information
mdtanrikulu authored Dec 14, 2023
2 parents d9dd554 + 3d3705f commit baa01b7
Show file tree
Hide file tree
Showing 14 changed files with 140 additions and 66 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

strategy:
matrix:
node-version: [16.x, 18.x]
node-version: [18.x, 19.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
Expand Down
2 changes: 1 addition & 1 deletion app_template.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
runtime: nodejs16 # or another supported version
runtime: nodejs18 # or another supported version

instance_class: F2

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
},
"dependencies": {
"@adraffy/ens-normalize": "^1.9.0",
"@ensdomains/ens-avatar": "^0.2.5",
"@ensdomains/ensjs": "^3.0.0-alpha.61",
"@ensdomains/ens-avatar": "^0.3.2",
"@ensdomains/ensjs": "^3.0.0-alpha.67",
"@types/lodash": "^4.14.170",
"btoa": "^1.2.1",
"canvas": "^2.11.2",
Expand Down Expand Up @@ -80,6 +80,6 @@
]
},
"volta": {
"node": "16.15.0"
"node": "18.15.0"
}
}
Binary file removed src/assets/DejaVuSans-Bold.ttf
Binary file not shown.
4 changes: 3 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const INAMEWRAPPER = process.env.INAMEWRAPPER || '0xd82c42d8';

const IPFS_GATEWAY = process.env.IPFS_GATEWAY || 'https://ipfs.io';
const INFURA_API_KEY = process.env.INFURA_API_KEY || '';
const OPENSEA_API_KEY = process.env.OPENSEA_API_KEY || '';
const NODE_PROVIDER = process.env.NODE_PROVIDER || 'geth';
const NODE_PROVIDER_URL = process.env.NODE_PROVIDER_URL || 'http://localhost:8545';

Expand All @@ -32,7 +33,7 @@ const ETH_REGISTRY_ABI = [
];

// response timeout: 1 min
const RESPONSE_TIMEOUT = 10 * 1000;
const RESPONSE_TIMEOUT = 15 * 1000;

export {
ADDRESS_ETH_REGISTRAR,
Expand All @@ -44,6 +45,7 @@ export {
INAMEWRAPPER,
IPFS_GATEWAY,
INFURA_API_KEY,
OPENSEA_API_KEY,
REDIS_URL,
NODE_PROVIDER,
NODE_PROVIDER_URL,
Expand Down
10 changes: 6 additions & 4 deletions src/controller/ensImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,12 @@ export async function ensImage(req: Request, res: Response) {
error instanceof NamehashMismatchError ||
error instanceof UnsupportedNetwork
) {
res.status(errCode).json({
message: error.message,
});
return;
if (!res.headersSent) {
res.status(errCode).json({
message: error.message,
});
return;
}
}

/* #swagger.responses[404] = {
Expand Down
10 changes: 6 additions & 4 deletions src/controller/ensMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,12 @@ export async function ensMetadata(req: Request, res: Response) {
error instanceof NamehashMismatchError ||
error instanceof UnsupportedNetwork
) {
res.status(errCode).json({
message: error.message,
});
return;
if (!res.headersSent) {
res.status(errCode).json({
message: error.message,
});
return;
}
}

try {
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ app.use(
'https://unpkg.com/redoc@latest/bundles/redoc.standalone.js'
],
imgSrc: ['*', 'data:'],
styleSrc: ["'self'", "'unsafe-inline'"],
fontSrc: ["'self'", 'data:'],
styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'],
connectSrc: ['*', 'data:'],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
frameSrc: ["'none'"],
childSrc: ["'none'"],
workerSrc: ["'none'"],
workerSrc: ['blob:'],
baseUri: ["'none'"],
formAction: ["'none'"],
upgradeInsecureRequests: [],
Expand Down
13 changes: 11 additions & 2 deletions src/service/avatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
RetrieveURIFailed,
TextRecordNotFound,
} from '../base';
import { IPFS_GATEWAY } from '../config';
import {
IPFS_GATEWAY,
OPENSEA_API_KEY
} from '../config';
import { abortableFetch } from '../utils/abortableFetch';

const window = new JSDOM('').window;
Expand Down Expand Up @@ -46,7 +49,13 @@ export class AvatarMetadata {
avtResolver: AvatarResolver;
constructor(provider: ethers.providers.BaseProvider, uri: string) {
this.defaultProvider = provider;
this.avtResolver = new AvatarResolver(provider, { ipfs: IPFS_GATEWAY });
this.avtResolver = new AvatarResolver(provider,
{
ipfs: IPFS_GATEWAY,
apiKey: { opensea: OPENSEA_API_KEY },
urlDenyList: [ 'metadata.ens.domains' ]
}
);
this.uri = uri;
}

Expand Down
28 changes: 28 additions & 0 deletions src/service/metadata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import avaTest, { ExecutionContext, TestFn } from 'ava';
import { TestContext } from '../../mock/interface';
import { Metadata } from './metadata';
import { Version } from '../base';

const test = avaTest as TestFn<TestContext>;

test('should compute metadata correctly', async (t: ExecutionContext<TestContext>) => {
const nickMetadataObj = {
name: 'nick.eth',
description: 'nick.eth, an ENS name.',
created_date: 1571924851000,
tokenId: '0x5d5727cb0fb76e4944eafb88ec9a3cf0b3c9025a4b2f947729137c5d7f84f68f',
version: Version.v1,
last_request_date: Date.now()
};
const testMetadata = new Metadata(nickMetadataObj);

t.is(testMetadata.name, nickMetadataObj.name);
t.is(testMetadata.description, nickMetadataObj.description);
t.is(testMetadata.attributes[0].value, nickMetadataObj.created_date * 1000);
t.is(testMetadata.version, Version.v1);
});

test('should return correct font size', async (t: ExecutionContext<TestContext>) => {
const textSize = Metadata._getFontSize('nick.eth');
t.is(textSize, 32);
});
69 changes: 45 additions & 24 deletions src/service/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import {ens_normalize} from '@adraffy/ens-normalize';
import {
CanvasRenderingContext2D,
createCanvas,
registerFont
} from 'canvas';
import { Version } from '../base';
import {
CANVAS_FONT_PATH,
Expand All @@ -9,19 +14,13 @@ import base64EncodeUnicode from '../utils/base64encode';
import { isASCII, findCharacterSet } from '../utils/characterSet';
import { getCodePointLength, getSegmentLength } from '../utils/charLength';

// no ts declaration files
const { createCanvas, registerFont } = require('canvas');


try {
registerFont(CANVAS_FONT_PATH, { family: 'Satoshi' });
registerFont(CANVAS_EMOJI_FONT_PATH, { family: 'Noto Color Emoji' });
} catch(error) {
console.warn("Font registeration is failed.");
console.warn(error);
interface Attribute {
trait_type: string,
display_type: string,
value: any
}


export interface MetadataInit {
name : string;
description? : string;
Expand All @@ -36,7 +35,7 @@ export interface MetadataInit {
export interface Metadata {
name : string;
description : string;
attributes : object[];
attributes : Attribute[];
name_length? : number;
segment_length? : number;
image : string;
Expand All @@ -51,20 +50,24 @@ export interface Metadata {

export class Metadata {
static MAX_CHAR = 60;
static ctx: CanvasRenderingContext2D;

constructor({
name,
description,
created_date,
tokenId,
version,
last_request_date
last_request_date,
}: MetadataInit) {
const label = this.getLabel(name);
this.is_normalized = this._checkNormalized(name);
this.name = this.formatName(name, tokenId);
this.description = this.formatDescription(name, description);
this.attributes = this.initializeAttributes(created_date, label);
this.url = this.is_normalized ? `https://app.ens.domains/name/${name}` : null;
this.url = this.is_normalized
? `https://app.ens.domains/name/${name}`
: null;
this.last_request_date = last_request_date;
this.version = version;
}
Expand All @@ -84,19 +87,23 @@ export class Metadata {

formatDescription(name: string, description?: string) {
const baseDescription = description || `${this.name}, an ENS name.`;
const normalizedNote = !this.is_normalized ? ` (${name} is not in normalized form)` : '';
const normalizedNote = !this.is_normalized
? ` (${name} is not in normalized form)`
: '';
const asciiWarning = this.generateAsciiWarning(this.getLabel(name));
return `${baseDescription}${normalizedNote}${asciiWarning}`;
}

generateAsciiWarning(label: string) {
if (!isASCII(label)) {
return ' ⚠️ ATTENTION: This name contains non-ASCII characters as shown above. ' +
return (
' ⚠️ ATTENTION: This name contains non-ASCII characters as shown above. ' +
'Please be aware that there are characters that look identical or very ' +
'similar to English letters, especially characters from Cyrillic and Greek. ' +
'Also, traditional Chinese characters can look identical or very similar to ' +
'simplified variants. For more information: ' +
'https://en.wikipedia.org/wiki/IDN_homograph_attack';
'https://en.wikipedia.org/wiki/IDN_homograph_attack'
);
}
return '';
}
Expand Down Expand Up @@ -129,7 +136,7 @@ export class Metadata {
];
}

addAttribute(attribute: object) {
addAttribute(attribute: Attribute) {
this.attributes.push(attribute);
}

Expand All @@ -152,7 +159,12 @@ export class Metadata {

const { domain, subdomainText } = this.processSubdomain(name, isSubdomain);
const { processedDomain, domainFontSize } = this.processDomain(domain);
const svg = this._generateByVersion(domainFontSize, subdomainText, isSubdomain, processedDomain);
const svg = this._generateByVersion(
domainFontSize,
subdomainText,
isSubdomain,
processedDomain
);

try {
this.setImage('data:image/svg+xml;base64,' + base64EncodeUnicode(svg));
Expand All @@ -165,7 +177,7 @@ export class Metadata {
processSubdomain(name: string, isSubdomain: boolean) {
let subdomainText;
let domain = name;

if (isSubdomain && !name.includes('...')) {
const labels = name.split('.');
let subdomain = labels.slice(0, labels.length - 2).join('.') + '.';
Expand Down Expand Up @@ -241,11 +253,20 @@ export class Metadata {
}

static _getFontSize(name: string): number {
const canvas = createCanvas(270, 270, 'svg');
const ctx = canvas.getContext('2d');
ctx.font =
'20px Satoshi, Noto Color Emoji, Apple Color Emoji, sans-serif';
const fontMetrics = ctx.measureText(name);
if (!this.ctx) {
try {
registerFont(CANVAS_FONT_PATH, { family: 'Satoshi' });
registerFont(CANVAS_EMOJI_FONT_PATH, { family: 'Noto Color Emoji' });
} catch (error) {
console.warn('Font registration is failed.');
console.warn(error);
}
const canvas = createCanvas(270, 270, 'svg');
this.ctx = canvas.getContext('2d');
this.ctx.font =
'20px Satoshi, Noto Color Emoji, Apple Color Emoji, sans-serif';
}
const fontMetrics = this.ctx.measureText(name);
const fontSize = Math.floor(20 * (200 / fontMetrics.width));
return fontSize < 34 ? fontSize : 32;
}
Expand Down
19 changes: 12 additions & 7 deletions src/utils/blockRecursiveCalls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@ export function blockRecursiveCalls(
const requestOrigin = req.get('origin') || req.get('referer');

if (requestOrigin) {
const parsedRequestOrigin = new URL(requestOrigin);
try {
const parsedRequestOrigin = new URL(requestOrigin);

if (
parsedRequestOrigin.hostname === req.hostname &&
parsedRequestOrigin.protocol.includes('http')
) {
console.warn(`Recursive call detected`);
res.status(403).json({ message: 'Recursive calls are not allowed.' });
if (
parsedRequestOrigin.hostname === req.hostname &&
parsedRequestOrigin.protocol.includes('http')
) {
console.warn(`Recursive call detected`);
res.status(403).json({ message: 'Recursive calls are not allowed.' });
return;
}
} catch (error) {
console.warn('Error parsing URL', error);
}
}
next();
Expand Down
4 changes: 2 additions & 2 deletions src/utils/rateLimiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ if (REDIS_URL) {

const opts = {
storeClient: redisClient,
points: 100, // Number of total points
duration: 5, // Per second(s)
points: 10, // Number of total points
duration: 2, // Per second(s)
execEvenly: false, // Do not delay actions evenly
blockDuration: 0, // Do not block the caller if consumed more than points
keyPrefix: 'ensrl', // Assign unique keys for each limiters with different purposes
Expand Down
Loading

0 comments on commit baa01b7

Please sign in to comment.