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

New Rule: Validate authentication in database query functions #151

Open
karlhorky opened this issue Jul 4, 2023 · 1 comment
Open

New Rule: Validate authentication in database query functions #151

karlhorky opened this issue Jul 4, 2023 · 1 comment
Assignees

Comments

@karlhorky
Copy link
Member

karlhorky commented Jul 4, 2023

Copied description from karlhorky/eslint-tricks#2


Examples of incorrect code for this rule:

// ❌ No sessionToken in first parameter
export const selectUserById = cache(async (id: User['id']) => {
  const [user] = await sql<User[]>`
    SELECT
      id,
      name
    FROM
      users
  `;

  return user;
});

// ❌ No Unauthenticated suffix
export const selectAnimals = cache(async () => {
  const animals = await sql<Animal[]>`
    SELECT
      *
    FROM
      animals
    ORDER BY
      id
  `;

  return animals;
});

Examples of correct code for this rule:

// ✅ sessionToken in first parameter and used in query
export const selectUserById = cache(async (sessionToken: Session['token'], id: User['id']) => {
  const [user] = await sql<User[]>`
    SELECT
      id,
      name
    FROM
      users
      INNER JOIN sessions ON (
        sessions.token = ${sessionToken}
        AND sessions.expiry_timestamp > now()
      )
  `;

  return user;
});

// ✅ Unauthenticated suffix
export const selectAnimalsUnauthenticated = cache(async () => {
  const animals = await sql<Animal[]>`
    SELECT
      *
    FROM
      animals
    ORDER BY
      id
  `;

  return animals;
});

ESLint-level validation to make sure that exported database query functions either use sessionToken or are marked ...Unauthenticated (also supports wrapping in React cache())

Version with no-restricted-syntax:

overrides: [
    {
      files: ['packages/database/queries/*.ts'],
      rules: {
        'no-restricted-syntax': [
          ...noRestrictedSyntaxOptions,
          // Enforce unambiguous exported database function patterns
          // (require either accepting a session token ("sessionToken")
          // as the first parameter or having a name ending with
          // "Unauthenticated")
          {
            selector:
              "ExportNamedDeclaration > FunctionDeclaration[id.name!=/Unauthenticated$/][params.0.name!='sessionToken'], ExportNamedDeclaration > VariableDeclaration[declarations.0.init.callee.name='cache'][declarations.0.id.name!=/Unauthenticated$/][declarations.0.init.arguments.0.params.0.name!='sessionToken']",
            message: `Ambiguous authentication of exported database query function - either pass \`sessionToken\` as the first parameter or name the function ending with \`Unauthenticated\`:

  function getUser(sessionToken: string, userId: number)
  const getUser = cache(async (sessionToken: string, userId: number) =>

  function getArticleCategoriesUnauthenticated()
  const getArticleCategoriesUnauthenticated = cache(async () =>

`,
          },
          // Enforce usage of session token ("sessionToken"
          // first parameter) within database functions
          {
            selector:
              "ExportNamedDeclaration > FunctionDeclaration[params.0.name='sessionToken'] > BlockStatement:not(:has([type='Identifier'][name='sessionToken'])), ExportNamedDeclaration > VariableDeclaration[declarations.0.init.callee.name='cache'][declarations.0.init.arguments.0.params.0.name='sessionToken'] > BlockStatement:not(:has([type='Identifier'][name='sessionToken']))",
            message:
              'Unused `sessionToken` parameter in database query function - use `sessionToken` in database queries to implement authentication and authorization',
          },
        ],
      },
    },
  ],

Examples in VS Code:

Screenshot 2023-06-10 at 15 04 34 Screenshot 2023-06-10 at 16 13 58

More examples:


MVP

First version done in:

@karlhorky
Copy link
Member Author

Example: this would have prevented missing authentication in this example here:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants