Skip to content
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

Refactor clerk/backend #2389

Merged
merged 9 commits into from
Dec 18, 2023
Merged

Refactor clerk/backend #2389

merged 9 commits into from
Dec 18, 2023

Conversation

nikosdouvlis
Copy link
Member

@nikosdouvlis nikosdouvlis commented Dec 18, 2023

Description

Review by PR

Checklist

  • npm test runs as expected.
  • npm run build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Packages affected

  • @clerk/backend
  • @clerk/chrome-extension
  • @clerk/clerk-js
  • @clerk/clerk-expo
  • @clerk/fastify
  • gatsby-plugin-clerk
  • @clerk/localizations
  • @clerk/nextjs
  • @clerk/clerk-react
  • @clerk/remix
  • @clerk/clerk-sdk-node
  • @clerk/shared
  • @clerk/themes
  • @clerk/types
  • build/tooling/chore

Copy link

changeset-bot bot commented Dec 18, 2023

🦋 Changeset detected

Latest commit: 99906da

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@nikosdouvlis nikosdouvlis mentioned this pull request Dec 18, 2023
24 tasks
@nikosdouvlis nikosdouvlis marked this pull request as ready for review December 18, 2023 12:14

class ClerkRequest extends Request {
clerkUrl: URL;
cookies: Map<string, string>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ could we make the clerkUrl and cookies read-only to avoid accidentally replacing them or expecting that updating them would affect our result.
I think that both ClerkRequest and AuthenticateContext should be read-only objects.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done :)

packages/eslint-config-custom/typescript.js Show resolved Hide resolved
test('is not CO with IPv6', assert => {
const originURL = new URL('http://[::1]');
const host = new URL('http://[::1]').host;
assert.false(checkCrossOrigin({ originURL, host }));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SokratisVidros did we remove the checkCrossOrigin (which was part of the interstitial) in the handshake?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, I've found no references in the codebase

packages/backend/src/tokens/authStatus.ts Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 I would suggest we keep types close to their usage and use types.ts for generic types related to specific package. Eg move RequestStateParams to tokens/authStatus.ts or tokens/request.ts

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to extract these to a new file to avoid circular dependencies between authStatus <> request, but let me take another look and see if I can avoid this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☝️

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need these for authenticateRequest and AuthenticateContext right now. Since we will continue refactoring the types and structs in clerk/backend after v5 as part of flex work, shall we fix this then?

@@ -0,0 +1,88 @@
import { constants } from '../constants';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These classes will be moved to a different dir when I wrap up my work on clerkMiddleware

* is in a signed in or signed out state or if we need
* to perform a handshake.
*/
class AuthenticateContext {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This naming feels weird as it looks like a React Context. Do we even need a class at this point? This is a very nit-picky comment, so you can ignore it for now, but I wanted to raise the point that the abstraction might not help as much as intended.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikosdouvlis Will we just pass a Request object afterall?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW this is a great candidate for async local storage.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... that the abstraction might not help as much as intended.
BTW this is a great candidate for async local storage.

Let me reply to these two together, as my intention is for us to use AuthenticateContext or a similar struct with Async Local Storage, but only after we simplify type-handling in clerk/backend and we decide that the abstraction works for us

I will not refactor anything else to use AuthenticateContext as I've simplified the problematic types for now. Let's do a design session and talk more about it

import { constants } from '../constants';

export type WithClerkUrl<T> = T & {
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Do we still need this in the handshake world?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need it for authenticateRequest and redirectToSignIn/protect from within clerkMiddleware and authMiddleware

Copy link
Member

@brkalow brkalow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice cleanup

Comment on lines +64 to +66
export const createClerkRequest = (...args: ConstructorParameters<typeof ClerkRequest>): ClerkRequest => {
return new ClerkRequest(...args);
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ What's the benefit of using a factory function here? If it's a straight pass-through to the constructor it feels unecessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a pattern I like but don't have strong opinions about. It's a way to export a class as type only while providing a factory so consumers can use ClerkRequest as a type but they are forced to initialize via createClerkRequest.

I find that this helps me better understand what is a type vs a constructor when going through code quickly, but as I said, no strong opinions here.

This is exported under /internals so we can iterate as much as we like as for now, it's a "private" API


interface AuthenticateContextInterface extends AuthenticateRequestOptions {
// header-based values
sessionTokenInHeader: string | undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ why do we use string | undefined instead of ?: string

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, the two variations are almost always interchangeable, with one semantic difference being that string | undefined would mean that the prop exists in the object but is undefined (aka "something" in obj would return true) while ?: string means that it might now exist at all ("something" in obj might return false)

Here, we're initializing the values and even if some of them will be undefined, they do exist on this. Extremely nitpicky difference here so happy to change if you prefer the alternative syntax.

sessionToken: string | undefined;
}

interface AuthenticateContext extends AuthenticateContextInterface {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why are we doing this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're merging the interface with the class so we don't have to redefine the interface's props in the class it self

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense 👍. In that case, I believe we can get rid of the intermediary AuthenticateContextInterface type (TS Playground).

This should work:

interface AuthenticateContext extends AuthenticateRequestOptions {
// all the properties defined in AuthenticateContextInterface above...
}

class AuthenticateContext {
// ...
}

packages/backend/src/tokens/__tests__/clerkRequest.test.ts Outdated Show resolved Hide resolved
Comment on lines 3 to 14
type RequestStateParams = {
publishableKey?: string;
domain?: string;
isSatellite?: boolean;
proxyUrl?: string;
signInUrl?: string;
signUpUrl?: string;
afterSignInUrl?: string;
afterSignUpUrl?: string;
};

export type AuthenticateRequestOptions = RequestStateParams & VerifyTokenOptions;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we consolidate these two types, or are we using RequestStateParams independently somewhere?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for spotting that, done!

A class that extends the native Request class, adds cookies helpers and a normalised clerkUrl that is constructed by using the values found in req.headers so it is able to work reliably when the app is running behind a proxy server.
A class that collects all data required to authenticate a request
@nikosdouvlis nikosdouvlis force-pushed the nikos/backend-refactor branch from 700c7e5 to 99906da Compare December 18, 2023 23:06
@nikosdouvlis nikosdouvlis merged commit ad7bc70 into main Dec 18, 2023
7 checks passed
@nikosdouvlis nikosdouvlis deleted the nikos/backend-refactor branch December 18, 2023 23:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants