Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions authorization-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,15 @@
"jsonwebtoken": "9.0.2",
"nestjs-pino": "3.5.0",
"passport": "0.7.0",
"passport-did-auth": "2.1.0-dev.1328.0",
"passport-did-auth": "2.1.0-dev.1340.0",
"passport-jwt": "4.0.1",
"pino-http": "8.6.1",
"pino-pretty": "10.3.1",
"reflect-metadata": "0.2.2",
"rxjs": "7.8.1",
"siwe": "2.3.2",
"swagger-ui-express": "5.0.1"
"swagger-ui-express": "5.0.1",
"tiny-lru": "^11.4.5"
},
"devDependencies": {
"@energyweb/eslint-config": "0.1.0",
Expand Down
1 change: 1 addition & 0 deletions authorization-server/src/modules/auth/auth.const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const CACHED_JSONRPC = Symbol('CACHED_JSONRPC');
53 changes: 53 additions & 0 deletions authorization-server/src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { RolesValidationService } from './roles-validation.service';
import { NonceService } from './nonce.service';
import { DisableBlockAuthRoutesMiddleware } from '../../middlewares/disable-block-auth-routes.middleware';
import { PinoLogger } from 'nestjs-pino';
import { providers } from 'ethers';
import { lru as createLru } from 'tiny-lru';
import { CACHED_JSONRPC } from './auth.const';

@Global()
@Module({
Expand All @@ -36,7 +39,57 @@ import { PinoLogger } from 'nestjs-pino';
NonceService,
RefreshTokenRepository,
RolesValidationService,
{
provide: CACHED_JSONRPC,
inject: [ConfigService],
useFactory: async (config: ConfigService) => {
const rpcUrl = config.get<string>('RPC_URL');
if (!rpcUrl) throw new Error('RPC_URL is not configured');

const cache = createLru(50, 12_000, true); // 12s expired 'next block'
const provider = new providers.StaticJsonRpcProvider(rpcUrl);

// keep original send
const originalSend = provider.send.bind(provider);

// patch send with per-request cache
provider.send = async (method: string, params: unknown[]) => {
// Only cache these read-only methods
const cacheableMethods = new Set([
'eth_call',
'eth_blockNumber',
'eth_chainId',
]);

if (!cacheableMethods.has(method)) {
return originalSend(method, params);
}

const key = JSON.stringify([method, params]); // simpler key
const entry = cache.get(key);

if (entry !== undefined) {
console.info(`[CACHE HIT] ${method}`);
return entry;
}

console.info(`[CACHE MISS] ${method} ${key}`);
const result = await originalSend(method, params);
cache.set(key, result);
return result;
};

(
provider as providers.JsonRpcProvider & { clearCache: () => void }
).clearCache = () => cache.clear();

return provider as providers.JsonRpcProvider & {
clearCache: () => void;
};
},
},
],
exports: [CACHED_JSONRPC],
})
export class AuthModule implements NestModule {
constructor(
Expand Down
78 changes: 33 additions & 45 deletions authorization-server/src/modules/auth/strategies/auth.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {
BadRequestException,
Inject,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { verifyCredential } from 'didkit-wasm-node';
import { providers } from 'ethers';
import { providers as EthersProviders } from 'ethers';
import { Request } from 'express';
import { JwtPayload } from 'jsonwebtoken';
import { PinoLogger } from 'nestjs-pino';
import {
chainConfigs,
DidStore,
DomainReader,
ethrReg,
InvalidSiweMessage,
Expand All @@ -22,17 +22,21 @@ import {
RoleIssuerResolver,
RoleRevokerResolver,
} from 'passport-did-auth';
import { CACHED_JSONRPC } from '../auth.const';
import { AuthService } from '../auth.service';

@Injectable()
export class AuthStrategy extends PassportStrategy(LoginStrategy, 'login') {
constructor(
private readonly logger: PinoLogger,
private readonly configService: ConfigService,
private readonly authService: AuthService,
@Inject(CACHED_JSONRPC)
private readonly _provider: EthersProviders.JsonRpcProvider & {
clearCache: () => void;
},
) {
const provider = new providers.JsonRpcProvider(
configService.get<string>('RPC_URL'),
);

const provider = _provider;
const domainReader = new DomainReader({
ensRegistryAddress: configService.get<string>('ENS_REGISTRY_ADDRESS'),
provider,
Expand All @@ -56,7 +60,6 @@ export class AuthStrategy extends PassportStrategy(LoginStrategy, 'login') {
privateKey: process.env.CACHE_SERVER_LOGIN_PRVKEY,
didContractAddress: process.env.DID_REGISTRY_ADDRESS,
ensRegistryAddress: process.env.ENS_REGISTRY_ADDRESS,
ipfsUrl: AuthStrategy.getIpfsClientConfig(configService).url,
includeAllRoles: configService.get<boolean>('INCLUDE_ALL_ROLES'),
siweMessageUri: new URL(
'/auth/login/siwe/verify',
Expand All @@ -72,11 +75,6 @@ export class AuthStrategy extends PassportStrategy(LoginStrategy, 'login') {
address: configService.get<string>('DID_REGISTRY_ADDRESS'),
method: Methods.Erc1056,
},
new DidStore({
baseURL: cacheServerUrl,
didPrefix: `did:${Methods.Erc1056}:${chainConfigs()[configService.get<number>('CHAIN_ID')].chainName}`,
privateKey: privateKey,
}),
privateKey,
cacheServerUrl,
),
Expand Down Expand Up @@ -109,10 +107,23 @@ export class AuthStrategy extends PassportStrategy(LoginStrategy, 'login') {
payload: unknown,
done: (err?: Error, user?: unknown, info?: unknown) => void,
): Promise<void> {
if (isJwtPayload(payload)) {
try {
await this.authService.identityTokenValidate(
(payload as JwtPayload).iat,
(payload as JwtPayload).exp,
);
} catch (error) {
done(error);
return;
}
}

return super.validate(
token,
payload,
(err?: Error, user?: unknown, info?: unknown) => {
// this._provider.clearCache();
if (
err?.message === 'Signature does not match address of the message.' ||
err?.message === 'uri in siwe message payload is incorrect'
Expand All @@ -128,40 +139,17 @@ export class AuthStrategy extends PassportStrategy(LoginStrategy, 'login') {
},
);
}

static getIpfsClientConfig(configService: ConfigService): {
url: string;
headers: Record<string, string> | null;
} {
let auth;

if (
configService.get<string>('IPFS_PROJECTID') &&
configService.get<string>('IPFS_PROJECTSECRET')
) {
auth =
'Basic ' +
Buffer.from(
configService.get<string>('IPFS_PROJECTID') +
':' +
configService.get<string>('IPFS_PROJECTSECRET'),
).toString('base64');
}

return {
url:
`${configService.get<string>('IPFS_PROTOCOL')}://` +
`${configService.get<string>('IPFS_HOST')}` +
`:${configService.get<string>('IPFS_PORT')}`,
headers: auth
? {
authorization: auth,
}
: null,
};
}
}

function parseAcceptedRoles(ACCEPTED_ROLES: string): string[] {
return ACCEPTED_ROLES ? ACCEPTED_ROLES.split(',') : [];
}

function isJwtPayload(payload: unknown): payload is JwtPayload {
return (
typeof payload === 'object' &&
payload !== null &&
'iat' in payload &&
'exp' in payload
);
}
Loading