Skip to content

Commit

Permalink
Merge branch 'main' of github.com:redwoodjs/redwood into feat/dc-rc-o…
Browse files Browse the repository at this point in the history
…g-gen-mw-p2

* 'main' of github.com:redwoodjs/redwood:
  feat(og-gen): Adds package and vite plugin for dynamic og generation (#10439)
  chore(deps): bump browserify-sign from 4.2.1 to 4.2.3 (#10446)
  chore(deps): bump tar from 6.1.11 to 6.2.1 in /docs (#10438)
  chore(deps): update dependency firebase to v10.11.0 (#10366)
  fix(auth): Handle when authorization header is lowercased (#10442)
  Update rbac.md - code match (#10405)
  chore: make crwa e2e test work across branches (#10437)
  feat: [Auth] Common AuthProvider & use* changes for middleware auth (#10420)
  • Loading branch information
dac09 committed Apr 12, 2024
2 parents 7dcce32 + e17fdd7 commit d901f65
Show file tree
Hide file tree
Showing 22 changed files with 684 additions and 387 deletions.
12 changes: 12 additions & 0 deletions .changesets/10420.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- feat: [Auth] Common AuthProvider & use* changes for middleware auth #10420 by @dac09 and @dthyresson

* First step of supporting Auth using middleware
* Ensure backwards compatibility with non-SSR auth

### Breaking Change

Removes `skipFetchCurrentUser` which was used by the no longer existing nHost auth provider, but could potentially have been used by custom auth.




1 change: 1 addition & 0 deletions .changesets/10439.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- feat(og-gen): Adds package and vite plugin for dynamic og generation (#10439) by @dac09
2 changes: 2 additions & 0 deletions .changesets/10442.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- fix(auth): Handle when authorization header is lowercased (#10442) by @dac09
Handles when 'authorization' header is lowercased, and adds some extra tests.
4 changes: 4 additions & 0 deletions docs/docs/tutorial/chapter7/rbac.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ Before we do that, we'll need to make sure that the web side has access to the r
```javascript title="api/src/lib/auth.js"
export const getCurrentUser = async (session) => {
if (!session || typeof session.id !== 'number') {
throw new Error('Invalid session')
}

return await db.user.findUnique({
where: { id: session.id },
// highlight-next-line
Expand Down
15 changes: 11 additions & 4 deletions docs/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8250,6 +8250,13 @@ __metadata:
languageName: node
linkType: hard

"minipass@npm:^5.0.0":
version: 5.0.0
resolution: "minipass@npm:5.0.0"
checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462
languageName: node
linkType: hard

"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2":
version: 2.1.2
resolution: "minizlib@npm:2.1.2"
Expand Down Expand Up @@ -10808,16 +10815,16 @@ __metadata:
linkType: hard

"tar@npm:^6.0.2, tar@npm:^6.1.2":
version: 6.1.11
resolution: "tar@npm:6.1.11"
version: 6.2.1
resolution: "tar@npm:6.2.1"
dependencies:
chownr: "npm:^2.0.0"
fs-minipass: "npm:^2.0.0"
minipass: "npm:^3.0.0"
minipass: "npm:^5.0.0"
minizlib: "npm:^2.1.1"
mkdirp: "npm:^1.0.3"
yallist: "npm:^4.0.0"
checksum: 10c0/5a016f5330f43815420797b87ade578e2ea60affd47439c988a3fc8f7bb6b36450d627c31ba6a839346fae248b4c8c12bb06bb0716211f37476838c7eff91f05
checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537
languageName: node
linkType: hard

Expand Down
70 changes: 70 additions & 0 deletions packages/api/src/auth/__tests__/parseAuthorizationHeader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { APIGatewayProxyEvent } from 'aws-lambda'
import { test, expect, describe } from 'vitest'

import { parseAuthorizationHeader } from '../index'

describe('parseAuthorizationHeader', () => {
test('throws error if Authorization header is not valid', () => {
const invalidHeaders = [
undefined,
null,
'',
'Bearer',
'Bearer ',
'Bearer token with spaces',
'Token',
'Token ',
'Token token with spaces',
]

invalidHeaders.forEach((header) => {
expect(() =>
// @ts-expect-error That's what we're testing
parseAuthorizationHeader({ headers: { Authorization: header } }),
).toThrowError('The `Authorization` header is not valid.')
})
})

test('returns the schema and token from valid Authorization header', () => {
const validHeaders = [
'Bearer token',
'Bearer 12345',
'Token token',
'Token 12345',
]

validHeaders.forEach((header) => {
// We only care about the headers in the event
const result = parseAuthorizationHeader({
headers: { Authorization: header },
} as unknown as APIGatewayProxyEvent)

expect(result).toEqual({
schema: header.split(' ')[0],
token: header.split(' ')[1],
})
})
})

test('Handles different lower-casing of the authorization header', () => {
const result = parseAuthorizationHeader({
headers: { authorization: 'Bearer bazinga' },
} as unknown as APIGatewayProxyEvent)

expect(result).toEqual({
schema: 'Bearer',
token: 'bazinga',
})
})

test('Handles different capital-casing of the Authorization header', () => {
const result = parseAuthorizationHeader({
headers: { Authorization: 'Bearer bazinga' },
} as unknown as APIGatewayProxyEvent)

expect(result).toEqual({
schema: 'Bearer',
token: 'bazinga',
})
})
})
2 changes: 1 addition & 1 deletion packages/api/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface AuthorizationHeader {
export const parseAuthorizationHeader = (
event: APIGatewayProxyEvent | Request,
): AuthorizationHeader => {
const parts = getEventHeader(event, 'authorization')?.split(' ')
const parts = getEventHeader(event, 'Authorization')?.split(' ')
if (parts?.length !== 2) {
throw new Error('The `Authorization` header is not valid.')
}
Expand Down
4 changes: 2 additions & 2 deletions packages/auth-providers/firebase/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@
"@babel/cli": "7.24.1",
"@babel/core": "^7.22.20",
"@types/react": "^18.2.55",
"firebase": "10.9.0",
"firebase": "10.11.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"react": "18.3.0-canary-a870b2d54-20240314",
"typescript": "5.4.3"
},
"peerDependencies": {
"firebase": "10.9.0"
"firebase": "10.11.0"
},
"gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1"
}
29 changes: 13 additions & 16 deletions packages/auth/src/AuthProvider/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { useToken } from './useToken.js'
import { useValidateResetToken } from './useValidateResetToken.js'

export interface AuthProviderProps {
skipFetchCurrentUser?: boolean
children: ReactNode
}

Expand Down Expand Up @@ -77,10 +76,7 @@ export function createAuthProvider<
) => (rolesToCheck: string | string[]) => boolean
},
) {
const AuthProvider = ({
children,
skipFetchCurrentUser,
}: AuthProviderProps) => {
const AuthProvider = ({ children }: AuthProviderProps) => {
// const [hasRestoredState, setHasRestoredState] = useState(false)

const serverAuthState = useContext(ServerAuthContext)
Expand All @@ -104,7 +100,6 @@ export function createAuthProvider<
authImplementation,
setAuthProviderState,
getCurrentUser,
skipFetchCurrentUser,
)

const hasRole = customProviderHooks?.useHasRole
Expand All @@ -115,13 +110,11 @@ export function createAuthProvider<
authImplementation,
setAuthProviderState,
getCurrentUser,
skipFetchCurrentUser,
)
const logIn = useLogIn(
authImplementation,
setAuthProviderState,
getCurrentUser,
skipFetchCurrentUser,
)
const logOut = useLogOut(authImplementation, setAuthProviderState)
const forgotPassword = useForgotPassword(authImplementation)
Expand All @@ -130,18 +123,22 @@ export function createAuthProvider<
const type = authImplementation.type
const client = authImplementation.client

// Whenever the authImplementation is ready to go, restore auth and
// reauthenticate
// Whenever the authImplementation is ready to go, restore auth and reauthenticate
useEffect(() => {
async function doRestoreState() {
// @MARK: this is where we fetch currentUser from graphql again
// because without SSR, initial state doesn't exist
// what we want to do here is to conditionally call reauthenticate
// so that the restoreAuthState comes from the injected state

await authImplementation.restoreAuthState?.()

// @MARK(SSR-Auth): Conditionally call reauthenticate, because initial
// state should come from server (on SSR).
// If the initial state didn't come from the server - or was restored
// already - reauthenticate will make a call to receive the current
// user from the server
reauthenticate()
// If the inital state didn't come from the server (or was restored before)
// reauthenticate will make an API call to the middleware to receive the current user
// (instead of called the graphql endpoint with currentUser)
if (!serverAuthState) {
reauthenticate()
}
}

doRestoreState()
Expand Down
2 changes: 0 additions & 2 deletions packages/auth/src/AuthProvider/useLogIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,11 @@ export const useLogIn = <
React.SetStateAction<AuthProviderState<TUser>>
>,
getCurrentUser: ReturnType<typeof useCurrentUser>,
skipFetchCurrentUser: boolean | undefined,
) => {
const reauthenticate = useReauthenticate(
authImplementation,
setAuthProviderState,
getCurrentUser,
skipFetchCurrentUser,
)

return useCallback(
Expand Down
16 changes: 5 additions & 11 deletions packages/auth/src/AuthProvider/useReauthenticate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export const useReauthenticate = <TUser>(
React.SetStateAction<AuthProviderState<TUser>>
>,
getCurrentUser: ReturnType<typeof useCurrentUser>,
skipFetchCurrentUser: boolean | undefined,
) => {
const getToken = useToken(authImplementation)

Expand Down Expand Up @@ -53,15 +52,16 @@ export const useReauthenticate = <TUser>(
client: authImplementation.client,
})
} else {
// This call here is a local check against the auth provider's client.
// e.g. if the auth sdk has logged you out, it'll throw an error
await getToken()

const currentUser = skipFetchCurrentUser ? null : await getCurrentUser()
const currentUser = await getCurrentUser()

setAuthProviderState((oldState) => ({
...oldState,
userMetadata,
currentUser,
isAuthenticated: true,
isAuthenticated: !!currentUser,
loading: false,
client: authImplementation.client,
}))
Expand All @@ -73,11 +73,5 @@ export const useReauthenticate = <TUser>(
error: e as Error,
})
}
}, [
authImplementation,
getToken,
setAuthProviderState,
skipFetchCurrentUser,
getCurrentUser,
])
}, [authImplementation, setAuthProviderState, getToken, getCurrentUser])
}
2 changes: 0 additions & 2 deletions packages/auth/src/AuthProvider/useSignUp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,11 @@ export const useSignUp = <
React.SetStateAction<AuthProviderState<TUser>>
>,
getCurrentUser: ReturnType<typeof useCurrentUser>,
skipFetchCurrentUser: boolean | undefined,
) => {
const reauthenticate = useReauthenticate(
authImplementation,
setAuthProviderState,
getCurrentUser,
skipFetchCurrentUser,
)

return useCallback(
Expand Down
37 changes: 3 additions & 34 deletions packages/auth/src/__tests__/AuthProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,40 +222,9 @@ describe('Custom auth provider', () => {
await waitFor(() => screen.getByText('Log In'))
})

test('Fetching the current user can be skipped', async () => {
const mockAuthClient = customTestAuth

render(
<AuthProvider skipFetchCurrentUser>
<AuthConsumer />
</AuthProvider>,
)

// We're booting up!
expect(screen.getByText('Loading...')).toBeInTheDocument()

// The user is not authenticated
await waitFor(() => screen.getByText('Log In'))
expect(mockAuthClient.getUserMetadata).toBeCalledTimes(1)

// Replace "getUserMetadata" with actual data, and login!
mockAuthClient.getUserMetadata = vi.fn(() => {
return {
sub: 'abcdefg|123456',
username: 'peterp',
}
})
fireEvent.click(screen.getByText('Log In'))

// Check that you're logged in!
await waitFor(() => screen.getByText('Log Out'))
expect(mockAuthClient.getUserMetadata).toBeCalledTimes(1)
expect(screen.getByText(/no current user data/)).toBeInTheDocument()

// Log out
fireEvent.click(screen.getByText('Log Out'))
await waitFor(() => screen.getByText('Log In'))
})
/// @MARK: Technically a breaking change.
// skipFetchCurrentUser used to be used for nHost only
// and isn't something we need to support anymore

/**
* This is especially helpful if you want to update the currentUser state.
Expand Down
5 changes: 1 addition & 4 deletions packages/create-redwood-app/tests/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@ describe('create-redwood-app', () => {
const p = await $`yarn create-redwood-app --version`

expect(p.exitCode).toEqual(0)
expect(p.stdout).toMatchInlineSnapshot(`
"7.0.0
[?25l[?25h"
`)
expect(p.stdout).toMatch(/\d+\.\d+\.\d+/)
expect(p.stderr).toMatchInlineSnapshot(`"[?25l[?25h"`)
})

Expand Down
1 change: 1 addition & 0 deletions packages/ogimage-gen/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!src/__fixtures__/**/dist
3 changes: 3 additions & 0 deletions packages/ogimage-gen/build.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { build } from '@redwoodjs/framework-tools'

await build()
3 changes: 3 additions & 0 deletions packages/ogimage-gen/cjsWrappers/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* eslint-env node */

module.exports = require('../dist/vite-plugin-ogimage-gen.js').default
Loading

0 comments on commit d901f65

Please sign in to comment.