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

[Bug?]: Context is empty #11576

Closed
1 task
razzeee opened this issue Sep 17, 2024 · 12 comments
Closed
1 task

[Bug?]: Context is empty #11576

razzeee opened this issue Sep 17, 2024 · 12 comments
Labels
bug/needs-info More information is needed for reproduction

Comments

@razzeee
Copy link
Contributor

razzeee commented Sep 17, 2024

What's not working?

Context is {} when using it similar to

The interesting this is, that we're still on redwood 7 and it seemed to break for no reason. I was guessing a dependency update, but wasn't able to pinpoint it so far.

It also seems to be fine in dev, but breaks for us with docker/deploy (our own docker stuff, pre redwood docker)

How do we reproduce the bug?

I'm lost on this so far, mostly want to reach out and see, if anything comes to mind. Might task a colleague to condense a repro repo, if nothing else happens.

What's your environment? (If it applies)

System:
    OS: Linux 6.10 Fedora Linux 40 (Workstation Edition)
    Shell: 5.9 - /usr/bin/zsh
  Binaries:
    Node: 20.16.0 - /tmp/xfs-6b43daa9/node
    Yarn: 4.4.1 - /tmp/xfs-6b43daa9/yarn
  Databases:
    SQLite: 3.45.1 - /usr/bin/sqlite3
  npmPackages:
    @redwoodjs/auth-custom-setup: 7.7.4 => 7.7.4 
    @redwoodjs/cli-data-migrate: 7.7.4 => 7.7.4 
    @redwoodjs/cli-storybook: 7.7.4 => 7.7.4 
    @redwoodjs/cli-storybook-vite: 7.7.4 => 7.7.4 
    @redwoodjs/core: 7.7.4 => 7.7.4 
  redwood.toml:
    [web]
      title = "slashskills"
      port = 8910
      apiUrl = "/.redwood/functions" # you can customise graphql and dbauth urls individually too: see https://redwoodjs.com/docs/app-configuration-redwood-toml#api-paths
      sourceMap = true
      includeEnvironmentVariables = [
        "GITLAB_CLIENT_ID",
        "GITLAB_REDIRECT_URI",
        "GITLAB_AUTHORITY",
        "SITE_ID",
        "BASE_URL",
        "SENTRY_DSN",
        "ENVIRONMENT",
        "SENTRY_ENABLED",
        "CI_COMMIT_SHA",
      ] # any ENV vars that should be available to the web side, see https://redwoodjs.com/docs/environment-variables#web
    [api]
      port = 8911
      host = "localhost"
    [browser]
      open = true

Are you interested in working on this?

  • I'm interested in working on this
@razzeee razzeee added the bug/needs-info More information is needed for reproduction label Sep 17, 2024
@ahaywood
Copy link
Contributor

@razzeee Thanks for reporting! This looks so strange. I'm tagging @Josh-Walker-GM and @Tobbe to see if they have any ideas or have seen anything similar?

@Tobbe
Copy link
Member

Tobbe commented Sep 18, 2024

Nothing that comes to mind right now I'm afraid

@cannikin
Copy link
Member

Any more details? That code snippet is within dbAuth itself, which you normally don’t have access to within an app. Where are you accessing context and seeing that it’s equal to {}?

@razzeee
Copy link
Contributor Author

razzeee commented Sep 18, 2024

I figured out how to get it to work again, but I'm not 100% sure, which part the actual fix was

We have had a docker setup since before it was available with redwood

RUN yarn global add @redwoodjs/cli prisma @sentry/cli && \
  prisma generate [...]

If I pin it like this, it starts working again:

RUN yarn global add @redwoodjs/cli@7.7.1 prisma@5.18.0 @sentry/cli && \

The prisma pin might actually not be needed, it started to work, after pinning the cli (we can probably pin something newer)

@arimendelow
Copy link
Contributor

@razzeee, can you provide a small repo with a reproduction of this issue? And I echo @cannikin's question — where are you attempting to access context?

The only time I've seen context be unexpectedly empty is when experiencing cookie issues, though without more information it's tough to know what the issue could be.

@razzeee
Copy link
Contributor Author

razzeee commented Sep 20, 2024

I'm not sure, that there is much to repro.

It seems like installing @redwoodjs/cli global in a major version different from what your targeting can cause this. Pretty specific and IMO expected.

Our problem is just, that we need it in the pipeline right now and can't sync the version from package.json to Dockerfile in an automatic way. I guess if data migrate works correctly now, we might not need that anymore, would need to find time to look into that.

@dthyresson
Copy link
Contributor

Jumping in a bit late here, but are you in v8 importing ...

import context from @redwoodjs/context ?

export let context: GlobalContext = createContextProxy({})

@razzeee
Copy link
Contributor Author

razzeee commented Oct 6, 2024

I don't think we're doing anything special there

@dthyresson
Copy link
Contributor

I don't think we're doing anything special there

Sorry, does that mean you are importing it with the context package or just relying on the global context from the global plugin.

sharing some code would help us understand how it’s used and might help us think of why it’s not behaving as you expect.

@razzeee
Copy link
Contributor Author

razzeee commented Oct 7, 2024

I'm not sure, how that code will help with this. As said, just using the correct version in our deploy build fixed it.

Here's our full api/src/lib/auth.ts

import https from 'https'

import axios from 'axios'
import { Role } from 'types/role'

import { AuthContextPayload } from '@redwoodjs/api'
import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server'

import {
  updateUserWithoutLogin,
  userByGitlabhandle,
} from 'src/services/users/users'

import Sentry from './sentry'

// As we only have a self signed cert, we need this
const axiosInstance = axios.create({
  httpsAgent: new https.Agent({
    rejectUnauthorized: false,
  }),
})

/**
 * getCurrentUser returns the user information together with
 * an optional collection of roles used by requireAuth() to check
 * if the user is authenticated or has role-based access
 *
 * !! BEWARE !! Anything returned from this function will be available to the
 * client--it becomes the content of `currentUser` on the web side (as well as
 * `context.currentUser` on the api side). You should carefully add additional
 * fields to the return object only once you've decided they are safe to be seen
 * if someone were to open the Web Inspector in their browser.
 *
 * @see https://github.com/redwoodjs/redwood/tree/main/packages/auth for examples
 *
 * @param _decoded - The decoded access token containing user info and JWT
 *   claims like `sub`. Note, this could be null.
 * @param { token, SupportedAuthTypes type } - The access token itself as well
 *   as the auth provider type
 * @param { APIGatewayEvent event, Context context } - An optional object which
 *   contains information from the invoker such as headers and cookies, and the
 *   context information about the invocation such as IP Address
 * @returns RedwoodUser
 */
export const getCurrentUser = async (
  _decoded: AuthContextPayload[0],
  { token }: AuthContextPayload[1],
  _invoker: AuthContextPayload[2]
) => {
  if (!token) {
    throw new Error('No token provided')
  }

  // ask gitlab for user data
  const user = await axiosInstance.get(
    `${process.env.GITLAB_AUTHORITY}/api/v4/user`,
    {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    }
  )

  if (user.status !== 200) {
    throw new Error('User not found in gitlab or gitlab unavailable')
  }

  if (user.data.external === true) {
    throw new ForbiddenError('External users are not allowed to login')
  }

  const userExists = await userByGitlabhandle({
    gitlabhandle: user.data.username,
  })

  Sentry.setUser({ username: user.data.username, id: user.data.id })

  if (userExists) {
    if (user.data.id !== userExists.gitlabId) {
      await updateUserWithoutLogin({
        gitlabhandle: userExists.gitlabhandle,
        input: {
          gitlabId: user.data.id,
        },
      })
    }

    await updateUserWithoutLogin({
      gitlabhandle: userExists.gitlabhandle,
      input: {
        lastSeen: new Date(),
      },
    })

    return {
      ...userExists,
      roles: userExists.userRole.map((r) => r.role.name),
      gitlabId: user.data.id,
      gitlabhandle: user.data.username,
      internalAvatar: userExists.userImage.find(
        (a) => a.imageType === 'INTERNAL_AVATAR' && a.deletedAt === null
      )?.imageBase64,
    }
  } else {
    throw new Error(`User not found: ${user.data.username}`)
  }
}

/**
 * The user is authenticated if there is a currentUser in the context
 *
 * @returns {boolean} - If the currentUser is authenticated
 */
export const isAuthenticated = (): boolean => {
  return !!context.currentUser
}

/**
 * When checking role membership, roles can be a single value, a list, or none.
 * You can use Prisma enums too (if you're using them for roles), just import your enum type from `@prisma/client`
 */
type AllowedRoles = Role | Role[] | undefined

/**
 * Checks if the currentUser is authenticated (and assigned one of the given roles)
 *
 * @param roles: {@link AllowedRoles} - Checks if the currentUser is assigned one of these roles
 *
 * @returns {boolean} - Returns true if the currentUser is logged in and assigned one of the given roles,
 * or when no roles are provided to check against. Otherwise returns false.
 */
export const hasRole = (roles: AllowedRoles): boolean => {
  if (!isAuthenticated()) {
    return false
  }

  const currentUserRoles = context.currentUser?.roles as Role[]

  if (typeof roles === 'string') {
    if (typeof currentUserRoles === 'string') {
      // roles to check is a string, currentUser.roles is a string
      return currentUserRoles === roles
    } else if (Array.isArray(currentUserRoles)) {
      // roles to check is a string, currentUser.roles is an array
      return currentUserRoles?.some((allowedRole) => roles === allowedRole)
    }
  }

  if (Array.isArray(roles)) {
    if (Array.isArray(currentUserRoles)) {
      // roles to check is an array, currentUser.roles is an array
      return currentUserRoles?.some((allowedRole) =>
        roles.includes(allowedRole)
      )
    } else if (typeof currentUserRoles === 'string') {
      // roles to check is an array, currentUser.roles is a string
      return roles.some((allowedRole) => currentUserRoles === allowedRole)
    }
  }

  // roles not found
  return false
}

/**
 * Use requireAuth in your services to check that a user is logged in,
 * whether or not they are assigned a role, and optionally raise an
 * error if they're not.
 *
 * @param roles?: {@link AllowedRoles} - When checking role membership, these roles grant access.
 *
 * @returns - If the currentUser is authenticated (and assigned one of the given roles)
 *
 * @throws {@link AuthenticationError} - If the currentUser is not authenticated
 * @throws {@link ForbiddenError} - If the currentUser is not allowed due to role permissions
 *
 * @see https://github.com/redwoodjs/redwood/tree/main/packages/auth for examples
 */
export const requireAuth = ({ roles }: { roles?: AllowedRoles } = {}) => {
  if (!isAuthenticated()) {
    throw new AuthenticationError("You don't have permission to do that.")
  }

  if (roles && !hasRole(roles)) {
    throw new ForbiddenError("You don't have access to do that.")
  }
}

@dthyresson
Copy link
Contributor

, just using the correct version in our deploy build fixed it.

Then can I close out the issue? Do we need to fix some docs or release notes?

Just trying to figure out next steps - the team rotates weekly on issue triage and want to make sure have enough info to pass on to get to a resolution.

@razzeee
Copy link
Contributor Author

razzeee commented Oct 7, 2024

I think we can close this.

We need to migrate to redwoods docker deploy at some point (does it do database and data migrations on startup?) and then it should hopefully not happen anymore, as it's handled by that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug/needs-info More information is needed for reproduction
Projects
None yet
Development

No branches or pull requests

6 participants