Is it possible to inject custom data into the context object with middleware?

I'm looking to implement the concept of Organizations with Roles and Permissions. I've seen this gist linked in a few discussions which seems like a nice starting point for how to organize the DB schema. I'm trying to figure out the best way to implement the backend. My current thought is to write custom middleware to check the user's selected organization and enrich the context object with the user's role and permissions to be passed into my Queries and Actions. If the user is not enrolled in the organization then the middleware would reject the request. Is this possible with Wasp? If not, any suggestions for the best way to approach this the "Wasp way"? I'd like to avoid writing custom apis for everything.
Gist
Flexible Multi-Tenant Web App Prisma Schema
Flexible Multi-Tenant Web App Prisma Schema. GitHub Gist: instantly share code, notes, and snippets.
14 Replies
kapa.ai
kapa.ai5w ago
Hi @vance_solar. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the provided knowledge sources, Wasp currently does not have built-in support for injecting custom data into the context object with middleware in the way you're describing. However, there are a few approaches you can consider to implement organization-based access control: 1. Manual checks in Operations: Currently, the recommended way to implement access control in Wasp is by manually checking the context.user object within each query and action. You can extend this approach to include organization and role checks. Auth Overview provides an example of how to use context.user:
export const createTask = async (task, context) => {
if (!context.user) {
throw new HttpError(403)
}
// Additional checks for organization and role can be added here
// ...
}

export const createTask = async (task, context) => {
if (!context.user) {
throw new HttpError(403)
}
// Additional checks for organization and role can be added here
// ...
}

2. Custom queries for user data: There's an open feature request Allow adding more info to the user object that gets passed to the Page which, if implemented, would allow you to enrich the user object with additional information like organization and role data. However, this is not currently available. 3. Implement your own middleware: While Wasp doesn't currently provide a built-in way to add custom middleware, there have been discussions about adding this feature. As mentioned in the Github Issue comments, the Wasp team is considering adding support for middleware, which could potentially be used for authorization purposes. For now, the most "Wasp-like" approach would be to implement your checks within each operation. You could create helper functions to centralize your authorization logic and call these helpers at the beginning of each operation. Regarding the database schema for organizations and roles, the gist you mentioned is indeed a good starting point. You can implement this schema in your Prisma model and then use it in your Wasp operations to perform the necessary checks. It's worth noting that the Wasp team is aware of the need for more robust authorization features. There are ongoing discussions and plans to improve this area of the framework, as evidenced by the Github Issue on Permissions (Access Control). For now, while it may require more manual work, you can implement your organization and role-based access control by extending the existing context.user checks in your operations and creating helper functions to centralize your logic. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @vance_solar, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
vance_solar
vance_solarOP5w ago
So if I add an Organization entity that contains one or more Users, how do I get the associated Organization for the User in the userAuth hook?
MEE6
MEE65w ago
Wohooo @vance_solar, you just became a Waspeteer level 1!
vance_solar
vance_solarOP5w ago
@kapa.ai So if I add an Organization entity that contains one or more Users, how do I get the associated Organization for the User in the userAuth hook?
kapa.ai
kapa.ai5w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To answer your question about getting the associated Organization for a User using the useAuth hook in Wasp, let's look at the available information and options: 1. User-Organization Relationship: First, you would need to set up a relationship between the User and Organization entities in your Prisma schema. This would typically be a many-to-many relationship, as a User can belong to multiple Organizations and an Organization can have multiple Users. 2. Using the useAuth hook: The useAuth hook in Wasp provides access to the current user's data. However, by default, it only includes the basic user information and doesn't automatically fetch related entities like Organizations. Auth Overview: Using the useAuth hook shows how to use the hook:
import { useAuth } from 'wasp/client/auth'

export function Main() {
const { data: user } = useAuth()
// ...
}
import { useAuth } from 'wasp/client/auth'

export function Main() {
const { data: user } = useAuth()
// ...
}
3. Fetching Organization data: To get the associated Organization(s) for the User, you have a couple of options: a. Create a custom query: You can create a separate query to fetch the Organization data for the current user. This query would use the user ID from the useAuth hook to retrieve the associated Organizations. b. Extend the user object: There's an open feature request to allow adding more info to the user object that gets passed to the Page, as mentioned in the Github Issue. However, this feature is not yet implemented. Given the current limitations, the most straightforward approach would be to create a custom query to fetch the Organization data. Here's a general outline of how you might implement this: 1. Create a new query in your Wasp file to fetch the user's organizations. 2. Implement the query logic in your operations file. 3. Use both useAuth and your custom query in your React component. Here's a conceptual example:
import { useAuth } from 'wasp/client/auth'
import { useQuery } from 'wasp/client/operations'

export function Main() {
const { data: user } = useAuth()
const { data: userOrganizations } = useQuery(getUserOrganizations, { userId: user?.id })

// Now you have access to both user and userOrganizations
// ...
}
import { useAuth } from 'wasp/client/auth'
import { useQuery } from 'wasp/client/operations'

export function Main() {
const { data: user } = useAuth()
const { data: userOrganizations } = useQuery(getUserOrganizations, { userId: user?.id })

// Now you have access to both user and userOrganizations
// ...
}
Remember that this approach will require an additional query to the server, which might impact performance if not managed carefully. In conclusion, while Wasp doesn't currently provide a built-in way to automatically include Organization data with the useAuth hook, you can work around this limitation by creating a custom query to fetch the required data. As Wasp continues to evolve, more robust solutions for handling complex relationships in authentication contexts may become available. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @vance_solar, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
vance_solar
vance_solarOP5w ago
@kapa.ai how do I customize the signup flow to create a new Organization and tie it to the user when the user signs up?
kapa.ai
kapa.ai5w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To customize the signup flow in Wasp to create a new Organization and tie it to the user when they sign up, you can follow the process of customizing the signup action. Here's how you can do it: 1. First, you'll need to create a custom signup action. This is described in the Auth Overview documentation. 2. In your main.wasp file, define your custom signup action:
action customSignup {
fn: import { signup } from "@src/auth/signup.js",
entities: [User]
}
action customSignup {
fn: import { signup } from "@src/auth/signup.js",
entities: [User]
}
3. Then, create a file src/auth/signup.js (or .ts if you're using TypeScript) where you'll implement your custom signup logic. Here's an example of how you might structure this:
import {
createProviderId,
sanitizeAndSerializeProviderData,
createUser,
} from 'wasp/server/auth'

export const signup = async (args, context) => {
try {
const providerId = createProviderId('email', args.email)
const providerData = await sanitizeAndSerializeProviderData({
hashedPassword: args.password,
isEmailVerified: false,
})

// Create the user
const user = await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)

// Create a new Organization and associate it with the user
const newOrganization = await context.entities.Organization.create({
data: {
name: args.organizationName, // Assuming you're passing this from the frontend
users: {
connect: { id: user.id }
}
}
})

// Your custom code after sign-up
// ...

return {
success: true,
message: 'User and Organization created successfully',
}
} catch (e) {
return {
success: false,
message: e.message,
}
}
}
import {
createProviderId,
sanitizeAndSerializeProviderData,
createUser,
} from 'wasp/server/auth'

export const signup = async (args, context) => {
try {
const providerId = createProviderId('email', args.email)
const providerData = await sanitizeAndSerializeProviderData({
hashedPassword: args.password,
isEmailVerified: false,
})

// Create the user
const user = await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)

// Create a new Organization and associate it with the user
const newOrganization = await context.entities.Organization.create({
data: {
name: args.organizationName, // Assuming you're passing this from the frontend
users: {
connect: { id: user.id }
}
}
})

// Your custom code after sign-up
// ...

return {
success: true,
message: 'User and Organization created successfully',
}
} catch (e) {
return {
success: false,
message: e.message,
}
}
}
This example assumes you have an Organization entity defined in your Prisma schema with a many-to-many relationship to User. 4. On the frontend, you'll need to modify your signup form to include the organization name field and use the custom signup action. Here's a simplified example:
import { customSignup } from 'wasp/client/operations'

export function Signup() {
const handleSubmit = async (event) => {
event.preventDefault()
try {
await customSignup({
email: email,
password: password,
organizationName: organizationName
})
// Handle successful signup
} catch (error) {
// Handle error
}
}

// Render your form here
}
import { customSignup } from 'wasp/client/operations'

export function Signup() {
const handleSubmit = async (event) => {
event.preventDefault()
try {
await customSignup({
email: email,
password: password,
organizationName: organizationName
})
// Handle successful signup
} catch (error) {
// Handle error
}
}

// Render your form here
}
Remember to adjust the code according to your specific entity relationships and requirements. This approach allows you to create both a User and an Organization in a single signup process, tying them together. For more detailed information on customizing the signup process, you can refer to the Username & Password Authentication documentation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @vance_solar, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
vance_solar
vance_solarOP4w ago
@kapa.ai how do I get the connection string for the default postgres instance created with a new wasp project?
kapa.ai
kapa.ai4w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To get the connection string for the default PostgreSQL instance created with a new Wasp project, you can follow these steps: 1. Start the database using the wasp start db command. 2. When you run this command, Wasp will output the connection string in the console. According to the Connecting to a Database documentation, you'll see something like this:
✨ Starting a PostgreSQL dev database (based on your Wasp config) ✨

Additional info:
ℹ Connection URL, in case you might want to connect with external tools:
postgresql://postgresWaspDevUser:postgresWaspDevPass@localhost:5432/todoApp-29a2c8c08a
✨ Starting a PostgreSQL dev database (based on your Wasp config) ✨

Additional info:
ℹ Connection URL, in case you might want to connect with external tools:
postgresql://postgresWaspDevUser:postgresWaspDevPass@localhost:5432/todoApp-29a2c8c08a
The connection string is provided in the "Connection URL" line. In this example, it would be:
postgresql://postgresWaspDevUser:postgresWaspDevPass@localhost:5432/todoApp-29a2c8c08a
postgresql://postgresWaspDevUser:postgresWaspDevPass@localhost:5432/todoApp-29a2c8c08a
This connection string includes all the necessary information: - Username: postgresWaspDevUser - Password: postgresWaspDevPass - Host: localhost - Port: 5432 - Database name: todoApp-29a2c8c08a (this will vary based on your project) You can use this connection string to connect to the database using external tools like DBeaver, as mentioned in the Discord support thread. Remember that this connection string is for the development database. For production, you'll need to set up and configure your own PostgreSQL database and provide its connection string via the DATABASE_URL environment variable. Connecting to a Database provides more details on database configuration in Wasp. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @vance_solar, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
vance_solar
vance_solarOP4w ago
@kapa.ai is it possible to share types that were defined in my app folder with the e2e-tests folder?
kapa.ai
kapa.ai4w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Based on the provided knowledge sources, there isn't specific information about sharing types between the app folder and the e2e-tests folder in Wasp projects. However, I can provide some general insights based on Wasp's structure and TypeScript support. Wasp does support TypeScript, as mentioned in the Discord support thread. The community members recommend using TypeScript for its benefits, especially in larger projects. While there's no direct information about sharing types between the app and e2e-tests folders, it's generally possible to share TypeScript types across different parts of a project. However, the exact method might depend on how Wasp structures its project and how it handles TypeScript compilation. One potential approach could be to define your shared types in a common location that both your app and e2e-tests can access. Then, you could import these types where needed. However, without specific information about Wasp's e2e testing setup, I can't provide a definitive answer on how to implement this. If you need a concrete solution, I would recommend: 1. Checking the Wasp documentation for any information about e2e testing and TypeScript configuration. 2. Reaching out to the Wasp community on their Discord channel or GitHub issues for specific guidance on this use case. I apologize that I couldn't provide a more definitive answer based on the available knowledge sources. The information about Wasp's handling of TypeScript in e2e testing contexts seems to be limited in the provided documents. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @vance_solar, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
vance_solar
vance_solarOP4w ago
@kapa.ai how can I reference/import a type that was declared outside the app folder?
kapa.ai
kapa.ai4w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Based on the available knowledge sources, there isn't specific information about referencing or importing types declared outside the app folder in Wasp projects. However, I can provide some general insights based on Wasp's structure and TypeScript support. Wasp does support TypeScript, as mentioned in the Discord support thread. However, the exact method of importing types from outside the app folder might depend on how Wasp structures its project and how it handles TypeScript compilation. One potential approach that Wasp seems to use for imports is the use of aliases. As mentioned in the Github Issue about import aliases, Wasp has considered using import aliases like @ext-src for absolute imports. This suggests that Wasp might have a way to configure import paths. Additionally, the Github Issue about exposing Prisma types mentions a proposal to make entity types available through imports like:
import { Task } from '@wasp/entities/Task'
import { Task } from '@wasp/entities/Task'
While this specific feature was about Prisma types, it suggests that Wasp has mechanisms for making types available through special import paths. However, without more specific information about Wasp's configuration for TypeScript and imports outside the app folder, I can't provide a definitive answer on how to implement this in your specific case. If you need a concrete solution, I would recommend: 1. Checking your project's tsconfig.json file (if it exists) for any path mappings or module resolution settings. 2. Looking for any Wasp-specific configuration files that might control import behavior. 3. Reaching out to the Wasp community on their Discord channel or GitHub issues for specific guidance on importing types from outside the app folder. I apologize that I couldn't provide a more definitive answer based on the available knowledge sources. The information about Wasp's handling of TypeScript imports from outside the app folder seems to be limited in the provided documents. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @vance_solar, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
miho
miho4w ago
@vance_solar how are things going? 🙂 Regarding your initial post:
My current thought is to write custom middleware to check the user's selected organization and enrich the context object with the user's role and permissions to be passed into my Queries and Actions
This is something we noticed it could be useful as well, it's a planned feature "operations middleware" : https://github.com/wasp-lang/wasp/issues/2396
GitHub
Allow specifying middleware for individual and groups of Operations...
If you want to execute some logic that is common for a group of Operations (and maybe also APIs), there is no simple way to do it -> we do have customMiddleware for APIs and api namespaces, but ...

Did you find this page helpful?