Update to @types/oauth2orize@1.11, fix type errors
This commit is contained in:
parent
2b23120664
commit
9c29880f8b
4 changed files with 36 additions and 31 deletions
|
@ -193,7 +193,7 @@
|
||||||
"@types/node-fetch": "3.0.3",
|
"@types/node-fetch": "3.0.3",
|
||||||
"@types/nodemailer": "6.4.8",
|
"@types/nodemailer": "6.4.8",
|
||||||
"@types/oauth": "0.9.1",
|
"@types/oauth": "0.9.1",
|
||||||
"@types/oauth2orize": "^1.8.11",
|
"@types/oauth2orize": "^1.11.0",
|
||||||
"@types/pg": "8.10.2",
|
"@types/pg": "8.10.2",
|
||||||
"@types/pug": "2.0.6",
|
"@types/pug": "2.0.6",
|
||||||
"@types/punycode": "2.1.0",
|
"@types/punycode": "2.1.0",
|
||||||
|
|
5
packages/backend/src/@types/oauth2orize-pkce.d.ts
vendored
Normal file
5
packages/backend/src/@types/oauth2orize-pkce.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
declare module 'oauth2orize-pkce' {
|
||||||
|
export default {
|
||||||
|
extensions(): any;
|
||||||
|
};
|
||||||
|
}
|
|
@ -5,8 +5,7 @@ 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 } from 'oauth2orize';
|
import oauth2orize, { type OAuth2, AuthorizationError, ValidateFunctionArity2, OAuth2Req } from 'oauth2orize';
|
||||||
import * as oauth2Query from 'oauth2orize/lib/response/query.js';
|
|
||||||
import oauth2Pkce from 'oauth2orize-pkce';
|
import oauth2Pkce from 'oauth2orize-pkce';
|
||||||
import expressSession from 'express-session';
|
import expressSession from 'express-session';
|
||||||
import fastifyView from '@fastify/view';
|
import fastifyView from '@fastify/view';
|
||||||
|
@ -45,12 +44,13 @@ function validateClientId(raw: string): URL {
|
||||||
// MUST contain a path component (new URL() implicitly adds one)
|
// MUST contain a path component (new URL() implicitly adds one)
|
||||||
|
|
||||||
// MUST NOT contain single-dot or double-dot path segments,
|
// MUST NOT contain single-dot or double-dot path segments,
|
||||||
// url.
|
|
||||||
const segments = url.pathname.split('/');
|
const segments = url.pathname.split('/');
|
||||||
if (segments.includes('.') || segments.includes('..')) {
|
if (segments.includes('.') || segments.includes('..')) {
|
||||||
throw new AuthorizationError('client_id must not contain dot path segments', 'invalid_request');
|
throw new AuthorizationError('client_id must not contain dot path segments', 'invalid_request');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (MAY contain a query string component)
|
||||||
|
|
||||||
// MUST NOT contain a fragment component
|
// MUST NOT contain a fragment component
|
||||||
if (url.hash) {
|
if (url.hash) {
|
||||||
throw new AuthorizationError('client_id must not contain a fragment component', 'invalid_request');
|
throw new AuthorizationError('client_id must not contain a fragment component', 'invalid_request');
|
||||||
|
@ -261,14 +261,9 @@ type OmitFirstElement<T extends unknown[]> = T extends [unknown, ...(infer R)]
|
||||||
? R
|
? R
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
interface OAuthRequest {
|
interface OAuthRequest extends OAuth2Req {
|
||||||
type: string;
|
|
||||||
clientID: string;
|
|
||||||
redirectURI: string;
|
|
||||||
state: string;
|
|
||||||
codeChallenge: string;
|
codeChallenge: string;
|
||||||
codeChallengeMethod: string;
|
codeChallengeMethod: string;
|
||||||
scope: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -323,17 +318,22 @@ export class OAuth2ProviderService {
|
||||||
scopes: string[],
|
scopes: string[],
|
||||||
}> = {};
|
}> = {};
|
||||||
|
|
||||||
const query = (txn, res, params) => {
|
|
||||||
// RFC 9207
|
|
||||||
// TODO: Oh no, perhaps returning to oidc-provider is better. Hacks everywhere here.
|
|
||||||
params.iss = config.url;
|
|
||||||
oauth2Query.default(txn, res, params);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.#server.grant(oauth2Pkce.extensions());
|
this.#server.grant(oauth2Pkce.extensions());
|
||||||
this.#server.grant(oauth2orize.grant.code({
|
this.#server.grant(oauth2orize.grant.code({
|
||||||
modes: { query },
|
modes: {
|
||||||
}, (client, redirectUri, token, ares, areq, done) => {
|
query: (txn, res, params) => {
|
||||||
|
// RFC 9207
|
||||||
|
params.iss = config.url;
|
||||||
|
|
||||||
|
const parsed = new URL(txn.redirectURI);
|
||||||
|
for (const [key, value] of Object.entries(params)) {
|
||||||
|
parsed.searchParams.append(key, value as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (res as any).redirect(parsed.toString());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, (client, redirectUri, token, ares, areq, locals, done) => {
|
||||||
(async (): Promise<OmitFirstElement<Parameters<typeof done>>> => {
|
(async (): Promise<OmitFirstElement<Parameters<typeof done>>> => {
|
||||||
console.log('HIT grant code:', client, redirectUri, token, ares, areq);
|
console.log('HIT grant code:', client, redirectUri, token, ares, areq);
|
||||||
const code = secureRndstr(32, true);
|
const code = secureRndstr(32, true);
|
||||||
|
@ -348,13 +348,13 @@ export class OAuth2ProviderService {
|
||||||
clientId: client.id,
|
clientId: client.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
redirectUri,
|
redirectUri,
|
||||||
codeChallenge: areq.codeChallenge,
|
codeChallenge: (areq as OAuthRequest).codeChallenge,
|
||||||
scopes: areq.scope,
|
scopes: areq.scope,
|
||||||
};
|
};
|
||||||
return [code];
|
return [code];
|
||||||
})().then(args => done(null, ...args), err => done(err));
|
})().then(args => done(null, ...args), err => done(err));
|
||||||
}));
|
}));
|
||||||
this.#server.exchange(oauth2orize.exchange.authorizationCode((client, code, redirectUri, body, done) => {
|
this.#server.exchange(oauth2orize.exchange.authorizationCode((client, code, redirectUri, body, authInfo, done) => {
|
||||||
(async (): Promise<OmitFirstElement<Parameters<typeof done>>> => {
|
(async (): Promise<OmitFirstElement<Parameters<typeof done>>> => {
|
||||||
const granted = TEMP_GRANT_CODES[code];
|
const granted = TEMP_GRANT_CODES[code];
|
||||||
console.log(granted, body, code, redirectUri);
|
console.log(granted, body, code, redirectUri);
|
||||||
|
@ -365,7 +365,7 @@ export class OAuth2ProviderService {
|
||||||
delete TEMP_GRANT_CODES[code];
|
delete TEMP_GRANT_CODES[code];
|
||||||
if (body.client_id !== granted.clientId) return [false];
|
if (body.client_id !== granted.clientId) return [false];
|
||||||
if (redirectUri !== granted.redirectUri) return [false];
|
if (redirectUri !== granted.redirectUri) return [false];
|
||||||
if (!body.code_verifier || pkceS256(body.code_verifier) !== granted.codeChallenge) return [false];
|
if (!body.code_verifier || pkceS256(body.code_verifier as string) !== granted.codeChallenge) return [false];
|
||||||
|
|
||||||
const accessToken = secureRndstr(128, true);
|
const accessToken = secureRndstr(128, true);
|
||||||
|
|
||||||
|
@ -383,7 +383,7 @@ export class OAuth2ProviderService {
|
||||||
permission: granted.scopes,
|
permission: granted.scopes,
|
||||||
});
|
});
|
||||||
|
|
||||||
return [accessToken, { scope: granted.scopes.join(' ') }];
|
return [accessToken, undefined, { scope: granted.scopes.join(' ') }];
|
||||||
})().then(args => done(null, ...args), err => done(err));
|
})().then(args => done(null, ...args), err => done(err));
|
||||||
}));
|
}));
|
||||||
this.#server.serializeClient((client, done) => done(null, client));
|
this.#server.serializeClient((client, done) => done(null, client));
|
||||||
|
@ -432,7 +432,7 @@ export class OAuth2ProviderService {
|
||||||
// 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 any).oauth2 as OAuth2;
|
||||||
console.log('HIT /oauth/authorize', request.query, oauth2, request.raw.session);
|
console.log('HIT /oauth/authorize', request.query, oauth2, (request.raw as any).session);
|
||||||
|
|
||||||
reply.header('Cache-Control', 'no-store');
|
reply.header('Cache-Control', 'no-store');
|
||||||
return await reply.view('oauth', {
|
return await reply.view('oauth', {
|
||||||
|
@ -458,11 +458,11 @@ export class OAuth2ProviderService {
|
||||||
await fastify.register(fastifyExpress);
|
await fastify.register(fastifyExpress);
|
||||||
// TODO: use redis session store to prevent memory leak
|
// TODO: use redis session store to prevent memory leak
|
||||||
fastify.use(expressSession({ secret: 'keyboard cat', resave: false, saveUninitialized: false }) as any);
|
fastify.use(expressSession({ secret: 'keyboard cat', resave: false, saveUninitialized: false }) as any);
|
||||||
fastify.use('/oauth/authorize', this.#server.authorization((areq: OAuthRequest, done: (err: Error | null, client?: any, redirectURI?: string) => void) => {
|
fastify.use('/oauth/authorize', this.#server.authorize(((areq, done) => {
|
||||||
(async (): Promise<OmitFirstElement<Parameters<typeof done>>> => {
|
(async (): Promise<OmitFirstElement<Parameters<typeof done>>> => {
|
||||||
console.log('HIT /oauth/authorize validation middleware', areq);
|
console.log('HIT /oauth/authorize validation middleware', areq);
|
||||||
|
|
||||||
const { codeChallenge, codeChallengeMethod, clientID, redirectURI, scope, type } = areq;
|
const { codeChallenge, codeChallengeMethod, clientID, redirectURI, scope, type } = areq as OAuthRequest;
|
||||||
|
|
||||||
const scopes = [...new Set(scope)].filter(s => kinds.includes(s));
|
const scopes = [...new Set(scope)].filter(s => kinds.includes(s));
|
||||||
if (!scopes.length) {
|
if (!scopes.length) {
|
||||||
|
@ -497,7 +497,7 @@ export class OAuth2ProviderService {
|
||||||
|
|
||||||
return [clientInfo, redirectURI];
|
return [clientInfo, redirectURI];
|
||||||
})().then(args => done(null, ...args), err => done(err));
|
})().then(args => done(null, ...args), err => done(err));
|
||||||
}));
|
}) as ValidateFunctionArity2));
|
||||||
// TODO: use mode: indirect
|
// TODO: use mode: indirect
|
||||||
// https://datatracker.ietf.org/doc/html/rfc6749.html#section-4.1.2.1
|
// https://datatracker.ietf.org/doc/html/rfc6749.html#section-4.1.2.1
|
||||||
// But make sure not to redirect to an invalid redirect_uri
|
// But make sure not to redirect to an invalid redirect_uri
|
||||||
|
|
|
@ -572,8 +572,8 @@ importers:
|
||||||
specifier: 0.9.1
|
specifier: 0.9.1
|
||||||
version: 0.9.1
|
version: 0.9.1
|
||||||
'@types/oauth2orize':
|
'@types/oauth2orize':
|
||||||
specifier: ^1.8.11
|
specifier: ^1.11.0
|
||||||
version: 1.8.11
|
version: 1.11.0
|
||||||
'@types/pg':
|
'@types/pg':
|
||||||
specifier: 8.10.2
|
specifier: 8.10.2
|
||||||
version: 8.10.2
|
version: 8.10.2
|
||||||
|
@ -7868,8 +7868,8 @@ packages:
|
||||||
resolution: {integrity: sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ==}
|
resolution: {integrity: sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/oauth2orize@1.8.11:
|
/@types/oauth2orize@1.11.0:
|
||||||
resolution: {integrity: sha512-eir5IGegpcnPuhnx1Asdxj3kDWWP/Qr1qkERMlDASwlEJM6pppVBxkW7ZvAX2H8eBHE+FP7lhg/iNlRrtNGewQ==}
|
resolution: {integrity: sha512-jmnP/Ip36XBzs+nIn/I8wNBZkQcn/agmp8K9V81he+wOllLYMec8T8AqbRPJCFbnFwaL03bbR8gI3CknMCXohw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/express': 4.17.17
|
'@types/express': 4.17.17
|
||||||
'@types/node': 20.3.1
|
'@types/node': 20.3.1
|
||||||
|
|
Loading…
Reference in a new issue