solve typescript warnings

This commit is contained in:
Kagami Sascha Rosylight 2023-06-16 22:24:03 +02:00
parent b81e6eeff9
commit 260ac0ecfc
2 changed files with 43 additions and 18 deletions

View file

@ -1,10 +1,11 @@
import dns from 'node:dns/promises'; import dns from 'node:dns/promises';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { ServerResponse } from 'node:http';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { JSDOM } from 'jsdom'; import { JSDOM } from 'jsdom';
import httpLinkHeader from 'http-link-header'; import httpLinkHeader from 'http-link-header';
import ipaddr from 'ipaddr.js'; import ipaddr from 'ipaddr.js';
import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req } from 'oauth2orize'; import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req, MiddlewareRequest } from 'oauth2orize';
import oauth2Pkce from 'oauth2orize-pkce'; import oauth2Pkce from 'oauth2orize-pkce';
import fastifyView from '@fastify/view'; import fastifyView from '@fastify/view';
import pug from 'pug'; import pug from 'pug';
@ -116,11 +117,23 @@ type OmitFirstElement<T extends unknown[]> = T extends [unknown, ...(infer R)]
? R ? R
: []; : [];
interface OAuthRequest extends OAuth2Req { interface OAuthParsedRequest extends OAuth2Req {
codeChallenge: string; codeChallenge: string;
codeChallengeMethod: string; codeChallengeMethod: string;
} }
interface OAuthHttpResponse extends ServerResponse {
redirect(location: string): void;
}
interface OAuth2DecisionRequest extends MiddlewareRequest {
body: {
transaction_id: string;
cancel: boolean;
login_token: string;
}
}
function getQueryMode(issuerUrl: string): oauth2orize.grant.Options['modes'] { function getQueryMode(issuerUrl: string): oauth2orize.grant.Options['modes'] {
return { return {
query: (txn, res, params): void => { query: (txn, res, params): void => {
@ -135,7 +148,7 @@ function getQueryMode(issuerUrl: string): oauth2orize.grant.Options['modes'] {
parsed.searchParams.append(key, value as string); parsed.searchParams.append(key, value as string);
} }
return (res as any).redirect(parsed.toString()); return (res as OAuthHttpResponse).redirect(parsed.toString());
}, },
}; };
} }
@ -143,7 +156,7 @@ function getQueryMode(issuerUrl: string): oauth2orize.grant.Options['modes'] {
class OAuth2Store { class OAuth2Store {
#cache = new MemoryKVCache<OAuth2>(1000 * 60 * 5); // 5min #cache = new MemoryKVCache<OAuth2>(1000 * 60 * 5); // 5min
load(req: any, cb: (err: Error | null, txn?: OAuth2) => void): void { load(req: OAuth2DecisionRequest, cb: (err: Error | null, txn?: OAuth2) => void): void {
const { transaction_id } = req.body; const { transaction_id } = req.body;
if (!transaction_id) { if (!transaction_id) {
cb(new AuthorizationError('Missing transaction ID', 'invalid_request')); cb(new AuthorizationError('Missing transaction ID', 'invalid_request'));
@ -157,13 +170,13 @@ class OAuth2Store {
cb(null, loaded); cb(null, loaded);
} }
store(req: unknown, oauth2: OAuth2, cb: (err: Error | null, transactionID?: string) => void): void { store(req: OAuth2DecisionRequest, oauth2: OAuth2, cb: (err: Error | null, transactionID?: string) => void): void {
const transactionId = secureRndstr(128, true); const transactionId = secureRndstr(128, true);
this.#cache.set(transactionId, oauth2); this.#cache.set(transactionId, oauth2);
cb(null, transactionId); cb(null, transactionId);
} }
remove(req: unknown, tid: string, cb: () => void): void { remove(req: OAuth2DecisionRequest, tid: string, cb: () => void): void {
this.#cache.delete(tid); this.#cache.delete(tid);
cb(); cb();
} }
@ -222,7 +235,7 @@ export class OAuth2ProviderService {
clientId: client.id, clientId: client.id,
userId: user.id, userId: user.id,
redirectUri, redirectUri,
codeChallenge: (areq as OAuthRequest).codeChallenge, codeChallenge: (areq as OAuthParsedRequest).codeChallenge,
scopes: areq.scope, scopes: areq.scope,
}); });
return [code]; return [code];
@ -285,7 +298,11 @@ export class OAuth2ProviderService {
// For now only allow the basic OAuth endpoints, to start small and evaluate // For now only allow the basic OAuth endpoints, to start small and evaluate
// this feature for some time, given that this is security related. // this feature for some time, given that this is security related.
fastify.get('/oauth/authorize', async (request, reply) => { fastify.get('/oauth/authorize', async (request, reply) => {
const oauth2 = (request.raw as any).oauth2 as OAuth2; const oauth2 = (request.raw as MiddlewareRequest).oauth2;
if (!oauth2) {
throw new Error('Unexpected lack of authorization information');
}
this.#logger.info(`Rendering authorization page for "${oauth2.client.name}"`); this.#logger.info(`Rendering authorization page for "${oauth2.client.name}"`);
reply.header('Cache-Control', 'no-store'); reply.header('Cache-Control', 'no-store');
@ -313,7 +330,7 @@ export class OAuth2ProviderService {
// This should return client/redirectURI AND the error, or // This should return client/redirectURI AND the error, or
// the handler can't send error to the redirection URI // the handler can't send error to the redirection URI
const { codeChallenge, codeChallengeMethod, clientID, redirectURI, scope, type } = areq as OAuthRequest; const { codeChallenge, codeChallengeMethod, clientID, redirectURI, scope, type } = areq as OAuthParsedRequest;
this.#logger.info(`Validating authorization parameters, with client_id: ${clientID}, redirect_uri: ${redirectURI}, scope: ${scope}`); this.#logger.info(`Validating authorization parameters, with client_id: ${clientID}, redirect_uri: ${redirectURI}, scope: ${scope}`);
@ -367,8 +384,9 @@ export class OAuth2ProviderService {
fastify.use('/oauth/decision', bodyParser.urlencoded({ extended: false })); fastify.use('/oauth/decision', bodyParser.urlencoded({ extended: false }));
fastify.use('/oauth/decision', this.#server.decision((req, done) => { fastify.use('/oauth/decision', this.#server.decision((req, done) => {
this.#logger.info(`Received the decision. Cancel: ${!!(req as any).body.cancel}`); const { body } = req as OAuth2DecisionRequest;
req.user = (req as any).body.login_token; this.#logger.info(`Received the decision. Cancel: ${!!body.cancel}`);
req.user = body.login_token;
done(null, undefined); done(null, undefined);
})); }));
fastify.use('/oauth/decision', this.#server.errorHandler()); fastify.use('/oauth/decision', this.#server.errorHandler());

View file

@ -224,7 +224,7 @@ describe('OAuth', () => {
}); });
assert.strictEqual(createResponse.status, 200); assert.strictEqual(createResponse.status, 200);
const createResponseBody: any = await createResponse.json(); const createResponseBody = await createResponse.json() as { createdNote: Note };
assert.strictEqual(createResponseBody.createdNote.text, 'test'); assert.strictEqual(createResponseBody.createdNote.text, 'test');
}); });
@ -258,20 +258,27 @@ describe('OAuth', () => {
const decisionResponseBob = await fetchDecisionFromResponse(responseBob, bob); const decisionResponseBob = await fetchDecisionFromResponse(responseBob, bob);
assert.strictEqual(decisionResponseBob.status, 302); assert.strictEqual(decisionResponseBob.status, 302);
const locationAlice = new URL(decisionResponseAlice.headers.get('location')!); const locationHeaderAlice = decisionResponseAlice.headers.get('location');
assert.ok(locationAlice.searchParams.has('code')); assert.ok(locationHeaderAlice);
const locationAlice = new URL(locationHeaderAlice);
const locationBob = new URL(decisionResponseBob.headers.get('location')!); const locationHeaderBob = decisionResponseBob.headers.get('location');
assert.ok(locationBob.searchParams.has('code')); assert.ok(locationHeaderBob);
const locationBob = new URL(locationHeaderBob);
const codeAlice = locationAlice.searchParams.get('code');
assert.ok(codeAlice);
const codeBob = locationBob.searchParams.get('code');
assert.ok(codeBob);
const tokenAlice = await client.getToken({ const tokenAlice = await client.getToken({
code: locationAlice.searchParams.get('code')!, code: codeAlice,
redirect_uri, redirect_uri,
code_verifier: pkceAlice.code_verifier, code_verifier: pkceAlice.code_verifier,
} as AuthorizationTokenConfigExtended); } as AuthorizationTokenConfigExtended);
const tokenBob = await client.getToken({ const tokenBob = await client.getToken({
code: locationBob.searchParams.get('code')!, code: codeBob,
redirect_uri, redirect_uri,
code_verifier: pkceBob.code_verifier, code_verifier: pkceBob.code_verifier,
} as AuthorizationTokenConfigExtended); } as AuthorizationTokenConfigExtended);