-
Notifications
You must be signed in to change notification settings - Fork 0
feat!: update openid-client to v6.x MONGOSH-2194 #218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@@ -127,7 +127,7 @@ export class OIDCTestProvider { | |||
const { port } = this.httpServer.address() as AddressInfo; | |||
|
|||
const app = express(); | |||
this.httpServer.on('request', app); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this because it's an async callback now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup! Just the linter complaining about the callback returning a Promise that is being ignored
@@ -80,26 +84,33 @@ export function normalizeObject<T extends object>(obj: T): T { | |||
return Object.fromEntries(Object.entries(obj).sort()) as T; | |||
} | |||
|
|||
function isURL(url: unknown): url is URL { | |||
return Object.prototype.toString.call(url).toLowerCase() === '[object url]'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious, is there a reason why url instanceof URL
would not work here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is mostly me being generally mis-trusting of instanceof
in JS since it won't work cross-context/cross-realm and there isn't usually a downside to using Object.prototype.toString
. It's annoying if it works most of the time but then breaks down when you're trying to do something else with it
Honestly I'd love to avoid having this check here at all, but didn't find a good way to do that since the primary alternative is duck type testing, i.e. whether properties like href
, hostname
, etc. are present on it, and that felt a bit too permissive
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@addaleax is this coming from your own experience or is there some reference of JS language features that wouldn't work as well across contexts? Is this about it being a later ES6 thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this has definitely been an issue occasionally 🙂 It's the fact that instanceof
performs equality comparisons along the prototype chain, but JS is not a language in which semantically identical classes actually compare equal.
Depending on the context you're working in, the two most common ways to run into this are a) having two different versions of a script in your source code, when the "same" class gets evaluated in each script it creates different instances that do not compare equal, or alternatively language or runtime built-ins from different realms (in JS specification language – this corresponds to vm.Context
in Node.js or e.g. iframes in browsers).
So yeah, instanceof
is often a code smell imo, it just checks the wrong thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, just another curiosity question
src/plugin.ts
Outdated
try { | ||
fetch = (await import('node-fetch')).default; | ||
} catch { | ||
fetch = await eval('import("node-fetch").default'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this again from issues of across different contexts? I'm curious how an eval would work as a fallback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah, this is something we already do in another place to ensure that this works both in webpack and in plain JS, with a bit more explanation:
Lines 102 to 127 in 0bb5f2b
// 'open' 9.x+ is ESM-only. However, TypeScript transpiles | |
// the `await import()` here to `require()`, which fails to load | |
// the package at runtime. We cannot use one of the typical workarounds | |
// for loading ESM packages unconditionally, because we need to be | |
// able to webpack this file (e.g. in Compass), which means that we | |
// need to use imports with constant string literal arguments. | |
// eslint-disable-next-line @typescript-eslint/consistent-type-imports | |
let open: typeof import('open').default; | |
try { | |
open = (await import('open')).default; | |
} catch (err: unknown) { | |
if ( | |
err && | |
typeof err === 'object' && | |
'code' in err && | |
err.code === 'ERR_REQUIRE_ESM' && | |
typeof __webpack_require__ === 'undefined' | |
) { | |
// This means that the import() above was transpiled to require() | |
// and that that require() called failed because it saw actual on-disk ESM. | |
// In this case, it should be safe to use eval'ed import(). | |
open = (await eval("import('open')")).default; | |
} else { | |
throw err; | |
} | |
} |
I'll add a comment to reference that section here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work! I have fairly minor comments, none of which are blocking merging this as-is.
src/plugin.ts
Outdated
headers: { ...options?.headers }, | ||
// TS is not convinced that node-fetch and built-in fetch are compatible enough | ||
} as Parameters<typeof fetch>[1])) as unknown as Response; | ||
}; | ||
|
||
private async getOIDCClient( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] should we rename this now that it's no longer returning a client?
})(); | ||
} | ||
|
||
serialize() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] should we explicitly specify the return type here? I know it gets inferred, but coming from strongly-typed languages, I tend to prefer more rigorous type definitions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I very much tend to have the same preference 🙂
In this case, it's definitely not easy to specify the return type, both because it's about as complex as the function definition itself and because TS helpers like Omit
do not play well with types like TokenEndpointResponse
which contain both fixed properties with types and arbitrarily indexed keys. I'll still add a comment about this here
} | ||
|
||
serialize() { | ||
const expiresIn: number | undefined = this.response.expiresIn(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] we seem to be doing a whole lot of arithmetic converting between expires_in
and expires_at
and skimming through the usages of TokenSet
, we don't ever seem to be using expiresIn()
or the entire response
for that matter. Would it make sense to make response
private and instead expose getters for the fields/properties that are actually used? I know this is a bit inconsequential of a refactoring and we already have the code, so happy to leave things as they are, I just feel it might be a bit more obviouos to readers/consumers if we limited the API surface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't ever seem to be using
expiresIn()
We do, when serializing 🙂 We could avoid it by adding overloads to the constructor that handle both the "value comes from openid-client and only has .expiresIn()
" and the "value comes from serialized storage and only has .expiresAt
" cases, but that feels like overkill here
Would it make sense to make response private and instead expose getters for the fields/properties that are actually used?
I like this suggestion regardless though, so I just pushed a change accounting for this in the last commit 👍
No description provided.