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

RSC: Add 'use client' to auth templates #10766

Merged
merged 3 commits into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client'

import { Auth0Client } from '@auth0/auth0-spa-js'

import { createAuth } from '@redwoodjs/auth-auth0-web'

const auth0 = new Auth0Client({
domain: process.env.AUTH0_DOMAIN || '',
clientId: process.env.AUTH0_CLIENT_ID || '',
authorizationParams: {
redirect_uri: process.env.AUTH0_REDIRECT_URI,
audience: process.env.AUTH0_AUDIENCE,
},

// Storing tokens in the browser's local storage provides persistence across page refreshes and browser tabs.
// But if an attacker can run JavaScript in your SPA using a cross-site scripting (XSS) attack,
// they can retrieve the tokens stored in local storage.
// See https://auth0.com/docs/libraries/auth0-spa-js#change-storage-options.
cacheLocation: 'localstorage',

// `useRefreshTokens` is required for automatically extending sessions beyond what's set in the initial JWT expiration.
// See https://auth0.com/docs/tokens/refresh-tokens.
// useRefreshTokens: true,
})

export const { AuthProvider, useAuth } = createAuth(auth0)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use client'

import { PublicClientApplication } from '@azure/msal-browser'

import { createAuth } from '@redwoodjs/auth-azure-active-directory-web'

const azureActiveDirectoryClient = new PublicClientApplication({
auth: {
clientId: process.env.AZURE_ACTIVE_DIRECTORY_CLIENT_ID || '',
authority: process.env.AZURE_ACTIVE_DIRECTORY_AUTHORITY,
redirectUri: process.env.AZURE_ACTIVE_DIRECTORY_REDIRECT_URI,
postLogoutRedirectUri:
process.env.AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI,
},
})

export const { AuthProvider, useAuth } = createAuth(azureActiveDirectoryClient)
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use client'

import React, { useEffect } from 'react'

import { ClerkProvider, useUser } from '@clerk/clerk-react'

import { createAuth } from '@redwoodjs/auth-clerk-web'
import { navigate } from '@redwoodjs/router'

export const { AuthProvider: ClerkRwAuthProvider, useAuth } = createAuth()

const ClerkStatusUpdater = () => {
const { isSignedIn, user, isLoaded } = useUser()
const { reauthenticate } = useAuth()

useEffect(() => {
if (isLoaded) {
reauthenticate()
}
}, [isSignedIn, user, reauthenticate, isLoaded])

return null
}

type ClerkOptions =
| { publishableKey: string; frontendApi?: never }
| { publishableKey?: never; frontendApi: string }

interface Props {
children: React.ReactNode
}

const ClerkProviderWrapper = ({
children,
clerkOptions,
}: Props & { clerkOptions: ClerkOptions }) => {
const { reauthenticate } = useAuth()

return (
<ClerkProvider
{...clerkOptions}
navigate={(to) => reauthenticate().then(() => navigate(to))}
>
{children}
<ClerkStatusUpdater />
</ClerkProvider>
)
}

export const AuthProvider = ({ children }: Props) => {
const publishableKey = process.env.CLERK_PUBLISHABLE_KEY
const frontendApi =
process.env.CLERK_FRONTEND_API_URL || process.env.CLERK_FRONTEND_API

const clerkOptions: ClerkOptions = publishableKey
? { publishableKey }
: { frontendApi }

return (
<ClerkRwAuthProvider>
<ClerkProviderWrapper clerkOptions={clerkOptions}>
{children}
</ClerkProviderWrapper>
</ClerkRwAuthProvider>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use client'

import { createAuthentication } from '@redwoodjs/auth'

// If you're integrating with an auth service provider you should delete this interface.
// Instead you should import the type from their auth client sdk.
export interface AuthClient {
login: () => User
logout: () => void
signup: () => User
getToken: () => string
getUserMetadata: () => User | null
}

// If you're integrating with an auth service provider you should delete this interface.
// This type should be inferred from the general interface above.
interface User {
// The name of the id variable will vary depending on what auth service
// provider you're integrating with. Another common name is `sub`
id: string
email?: string
username?: string
roles: string[]
}

// If you're integrating with an auth service provider you should delete this interface
// This type should be inferred from the general interface above
export interface ValidateResetTokenResponse {
error?: string
[key: string]: string | undefined
}

// Replace this with the auth service provider client sdk
const client = {
login: () => ({
id: 'unique-user-id',
email: 'email@example.com',
roles: [],
}),
signup: () => ({
id: 'unique-user-id',
email: 'email@example.com',
roles: [],
}),
logout: () => {},
getToken: () => 'super-secret-short-lived-token',
getUserMetadata: () => ({
id: 'unique-user-id',
email: 'email@example.com',
roles: [],
}),
}

function createAuth() {
const authImplementation = createAuthImplementation(client)

// You can pass custom provider hooks here if you need to as a second
// argument. See the Redwood framework source code for how that's used
return createAuthentication(authImplementation)
}

// This is where most of the integration work will take place. You should keep
// the shape of this object (i.e. keep all the key names) but change all the
// values/functions to use methods from the auth service provider client sdk
// you're integrating with
function createAuthImplementation(client: AuthClient) {
return {
type: 'custom-auth',
client,
login: async () => client.login(),
logout: async () => client.logout(),
signup: async () => client.signup(),
getToken: async () => client.getToken(),
/**
* Actual user metadata might look something like this
* {
* "id": "11111111-2222-3333-4444-5555555555555",
* "aud": "authenticated",
* "role": "authenticated",
* "roles": ["admin"],
* "email": "email@example.com",
* "app_metadata": {
* "provider": "email"
* },
* "user_metadata": null,
* "created_at": "2016-05-15T19:53:12.368652374-07:00",
* "updated_at": "2016-05-15T19:53:12.368652374-07:00"
* }
*/
getUserMetadata: async () => client.getUserMetadata(),
}
}

export const { AuthProvider, useAuth } = createAuth()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client'

import { createDbAuthClient, createAuth } from '@redwoodjs/auth-dbauth-web'

const dbAuthClient = createDbAuthClient()

export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use client'

import { createDbAuthClient, createAuth } from '@redwoodjs/auth-dbauth-web'
import WebAuthnClient from '@redwoodjs/auth-dbauth-web/webAuthn'

const dbAuthClient = createDbAuthClient({ webAuthn: new WebAuthnClient() })

export const { AuthProvider, useAuth } = createAuth(dbAuthClient)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client'

import { initializeApp, getApp, getApps } from 'firebase/app'
import * as firebaseAuth from 'firebase/auth'

import { createAuth } from '@redwoodjs/auth-firebase-web'

const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,

// Optional config, may be needed, depending on how you use firebase
// projectId: process.env.FIREBASE_PROJECT_ID,
// storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
// messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
// appId: process.env.FIREBASE_APP_ID,
}

const firebaseApp = ((config) => {
const apps = getApps()

if (!apps.length) {
initializeApp(config)
}

return getApp()
})(firebaseConfig)

export const firebaseClient = {
firebaseAuth,
firebaseApp, // optional
}

export const { AuthProvider, useAuth } = createAuth(firebaseClient)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client'

import netlifyIdentity from 'netlify-identity-widget'

import { createAuth } from '@redwoodjs/auth-netlify-web'
import { isBrowser } from '@redwoodjs/prerender/browserUtils'

isBrowser && netlifyIdentity.init()

export const { AuthProvider, useAuth } = createAuth(netlifyIdentity)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use client'

import { createClient } from '@supabase/supabase-js'

import { createAuth } from '@redwoodjs/auth-supabase-web'

const supabaseClient = createClient(
process.env.SUPABASE_URL || '',
process.env.SUPABASE_KEY || ''
)

export const { AuthProvider, useAuth } = createAuth(supabaseClient)
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client'

import SuperTokens, { SuperTokensWrapper } from 'supertokens-auth-react'
import Session from 'supertokens-auth-react/recipe/session'
import ThirdPartyEmailPassword, {
Github,
Google,
Apple,
} from 'supertokens-auth-react/recipe/thirdpartyemailpassword'
import { ThirdPartyEmailPasswordPreBuiltUI } from 'supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui'

import { createAuth } from '@redwoodjs/auth-supertokens-web'
import { isBrowser } from '@redwoodjs/prerender/browserUtils'

const websiteDomain =
process.env.SUPERTOKENS_WEBSITE_DOMAIN || 'http://localhost:8910'
const apiDomain = process.env.SUPERTOKENS_API_DOMAIN || websiteDomain
const apiGatewayPath =
process.env.SUPERTOKENS_API_GATEWAY_PATH || '/.redwood/functions'

const superTokensClient = {
sessionRecipe: Session,
redirectToAuth: SuperTokens.redirectToAuth,
}

export const PreBuiltUI = [ThirdPartyEmailPasswordPreBuiltUI]

isBrowser &&
SuperTokens.init({
appInfo: {
appName: process.env.SUPERTOKENS_APP_NAME,
apiDomain,
websiteDomain,
apiGatewayPath,
websiteBasePath: '/auth',
apiBasePath: '/auth',
},
recipeList: [
Session.init(),
ThirdPartyEmailPassword.init({
signInAndUpFeature: {
providers: [Github.init(), Google.init(), Apple.init()],
},
}),
],
})

const { AuthProvider: SuperTokensAuthProvider, useAuth } =
createAuth(superTokensClient)

interface Props {
children: React.ReactNode
}

const AuthProvider = ({ children }: Props) => {
return (
<SuperTokensWrapper>
<SuperTokensAuthProvider>{children}</SuperTokensAuthProvider>
</SuperTokensWrapper>
)
}

export { AuthProvider, useAuth }
12 changes: 11 additions & 1 deletion packages/cli-helpers/src/auth/__tests__/authTasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import fs from 'fs'
import path from 'path'

import { vol } from 'memfs'
import { vi, beforeEach, describe, it, expect, test } from 'vitest'
import { vi, afterAll, beforeEach, describe, it, expect, test } from 'vitest'

import { getPaths } from '../../lib/paths.js'
import { isTypeScriptProject } from '../../lib/project.js'
Expand Down Expand Up @@ -92,6 +92,12 @@ function platformPath(filePath: string) {
return filePath.split('/').join(path.sep)
}

const original_RWJS_CWD = process.env.RWJS_CWD

afterAll(() => {
process.env.RWJS_CWD = original_RWJS_CWD
})

beforeEach(() => {
vi.restoreAllMocks()
vi.mocked(isTypeScriptProject).mockReturnValue(true)
Expand All @@ -100,7 +106,10 @@ beforeEach(() => {
mockedPathGenerator('App.tsx', 'Routes.tsx'),
)

process.env.RWJS_CWD = getPaths().base

vol.fromJSON({
[path.join(getPaths().base, 'redwood.toml')]: '# redwood.toml',
[path.join(
getPaths().base,
platformPath('/templates/web/auth.ts.template'),
Expand Down Expand Up @@ -152,6 +161,7 @@ describe('authTasks', () => {
// though it was on the mock filesystem.
vol.reset()
vol.fromJSON({
[path.join(getPaths().base, 'redwood.toml')]: '# redwood.toml',
[getPaths().web.app]: webAppTsx,
[getPaths().api.graphql]: graphqlTs,
[getPaths().web.routes]: routesTsx,
Expand Down
Loading
Loading