The Context That Follows the User

This chapter examines how Ever Gauzy decides which tenant, organization, employee, role, and feature state a request belongs to. That question supports a practical judgment: whether a project, time record, invoice, or integration result is being read by the right person in the right organizational context. In Gauzy, authorization is not a final button placed after the data is loaded; it is part of the path that gives the data its meaning.

Four identities, one operating scope

The word “user” is too vague to describe what happens inside Gauzy. The repository separates at least four related ideas. A tenant is a workspace-level boundary. An organization is an operating entity within that tenant. A user is the authenticated account. An employee is the organization-specific working identity connected to that account. A team or department then provides a more local structure for assigning work and applying policies. A membership record connects a user to an organization rather than assuming that every account can see every organization in its workspace.

That separation solves a real problem. Imagine one consultant who works for two organizations managed through the same Gauzy installation. Their account can be the same in both places, but their employee record, team membership, projects, bill rate, time records, and permissions need not be. If Gauzy attached every work record directly to the account, it would be difficult to tell which organization owns a timesheet or which manager may review it. The organization-specific employee record provides that missing boundary.

The model also supports ordinary cases. A project team may contain several employees. A user may have a default organization and team for convenience, then switch to another organization. An administrator may manage records across employees, while an employee should normally work with their own records. The selected context is therefore not just a navigation preference. It selects the operating world in which a request is evaluated.

The relationships are visible in Gauzy’s domain model. A tenant owns organizations and role-permission definitions. An organization owns employees, projects, clients, invoices, payments, reports, and many other records. A user stores role and organization/team selections and has explicit organization memberships. An employee belongs to both a tenant and an organization and connects to a user. Employees then become the point of contact for projects, tasks, time logs, timesheets, expenses, goals, approvals, and time-off records.

This arrangement is more flexible than a single-company account model, but it creates a requirement for precision. “This person has a Gauzy account” does not imply “this person may read every Gauzy record.” The relevant question is: which account, which tenant, which organization, which employee identity, and which role are active for this operation?

How a request becomes authorized data

Gauzy’s server turns that question into a sequence of checks. The client sends an authenticated request, normally with a bearer token and the context needed to identify the selected tenant or organization. The API’s global authentication guard protects routes unless they are explicitly marked public; an HTTP preflight request is allowed through so the browser can negotiate CORS. Everything else must establish an identity before ordinary application logic is allowed to proceed.

The JWT strategy first rejects tokens that do not identify a user. It then loads the authenticated user. If the token contains an employee identity, Gauzy checks that the employee exists and belongs to that user. If the token contains an organization identity, Gauzy checks for an active user-to-organization membership in the relevant tenant before accepting that organization as the selected context. The strategy places the selected organization back onto the request user. This is important because later services can use a validated context instead of trusting an arbitrary organization identifier supplied by a client.

A request context keeps that information available across the request lifecycle. Gauzy creates a context through continuation-local storage, a mechanism that lets asynchronous services access request-specific state without passing a long argument list through every function. The context includes a correlation identifier for the request and gives application code access to the current user, tenant, organization, role, employee, language, IP address, and user agent. If a caller supplied a correlation ID, the middleware can preserve it; otherwise it creates one. That supports tracing a request while keeping its authorization state attached to the same execution.

The frontend participates in this context flow. Angular interceptors attach the access token, active tenant, language, and other common request information, and an interceptor can refresh an expired token after a failed request. The interface also loads session, tenant, and permission state when it starts. These conveniences make the browser consistent with the selected workspace, but they do not make the browser authoritative. A hidden menu item is a usability feature. The server-side checks determine whether an action is actually allowed.

The next layer is tenant-aware data access. Gauzy’s shared CRUD service recognizes entities that contain a tenant boundary and automatically adds the current tenant to ordinary reads. For records that also contain an employee boundary, it can restrict an employee to their own records unless they hold the permission to change the selected employee. On create, it stamps the current tenant and, where appropriate, the current employee onto the new record. On update-through-create operations, it checks whether an existing identifier belongs to another tenant before allowing the save. That last check matters because a database upsert keyed only by an identifier could otherwise turn a foreign row into a row belonging to the caller’s tenant.

The result is a layered definition of scope rather than one filter. Authentication answers “who is this?” Membership answers “which organization may this account enter?” Request context answers “which scope is active during this execution?” Tenant-aware services answer “which rows can this operation address?” Employee filtering answers “which person’s records can this user manage?” Soft deletion and audit-by-user fields add history and recovery semantics, but they do not replace authorization.

It is useful to ask where each fact is authoritative. The token is evidence of a signed identity, not a free-form declaration of what the caller may access. The membership record is the database-backed evidence that the identity may enter an organization. The request context is the server’s short-lived working copy of those validated facts. The data service owns the final row filter, because it can apply tenant and employee conditions at the point where a query or write is executed. The browser stores and sends context so the application feels consistent, but it should be treated as a requester, not as the owner of policy.

This division of ownership matters most on writes. A client can include an organization or tenant value in a payload, but Gauzy’s tenant-aware service enriches records from the current request context and checks existing identifiers before an upsert. In conceptual terms, the server refuses to let a caller rename a row’s ownership merely by presenting a foreign identifier with a new payload. The check is not a replacement for route-level permissions; it is a second line of defense at the data boundary. Similarly, soft deletion preserves a record’s deletion state and can support recovery, but a recoverable record is not automatically visible or editable to every user.

The boundary is logical rather than necessarily physical. A tenant does not automatically mean a separate database, process, or deployment. That makes shared infrastructure possible, but it also makes correct predicates and membership checks important. A failure in context propagation can be a cross-domain failure: a task query, an income report, or a GitHub synchronization may all be pointed at the wrong organization even though the underlying feature code is working. Correlation IDs help operators follow such behavior across a request, but observability can show that a wrong decision happened; it cannot make the decision correct.

Permissions, features, and the meaning of rejection

Role permissions add a policy layer above basic identity and scope. Gauzy’s role and role-permission entities associate a role with named permissions, and the permission guard checks the requested permissions in the current tenant and role context. Permission results are cached for a short period, which reduces repeated database checks while keeping permission changes from becoming permanent. The guard can therefore distinguish a valid authenticated user from a user authorized for a particular operation.

Organization-level permissions add a different question. For employee roles, Gauzy can inspect permissions represented on the employee’s organization and check them against the current tenant and employee. This is useful when an organization’s policy should control access to an area such as income, time tracking, or another operational feature independently of a global role definition. A manager’s ability to manage a set of employees can also influence whether they may retrieve or alter those employees’ time records.

Feature flags are different again. A permission asks whether this actor may perform an operation. A feature flag asks whether the operation is available in the current product configuration. Gauzy stores feature definitions and organization-specific enabled state, then a feature guard can make a disabled route behave as unavailable. This lets a deployment or organization turn off a capability without deleting its underlying domain data. It also means that “the endpoint exists in the code” and “the endpoint is available here” are separate statements.

A concrete example shows why the layers must remain separate. Suppose a consultant belongs to Organization A and Organization B. They switch to Organization B in the web interface and request their tasks. The token identifies the account; the organization membership validates entry; the request context records the selected tenant and organization; the task service applies the tenant and employee scope; and the permission layer decides whether the consultant may see only their own tasks or a wider set. If the same request is made after switching to Organization A, the result can legitimately differ even though the person and browser session are unchanged.

A second example involves a manager reviewing a timesheet. A manager may be authenticated and a member of the organization, but still lack the permission to change the selected employee. In that case, Gauzy’s employee-aware service logic can narrow the query to the manager’s own employee identity unless a management rule allows access to the requested employees. The system is not merely checking whether the manager can open the timesheet page; it is checking whether the requested records fit the manager’s current authority.

The design has limits that a responsible operator should test. Gauzy contains several guard types and controller-specific combinations, so no single description proves that every route has identical policy behavior. The organization-permission guard currently treats non-employee roles as authorized in one branch and includes a code comment questioning that decision. That is an explicit review point, not a detail to conceal behind the general presence of permission guards. It means a deployment assessment should inspect the actual role, route, and guard combination for sensitive operations.

The same caution applies to tenant headers and client-side state. A client may send a tenant or organization value, but the server must validate it against authenticated membership and apply its own scoping. A UI that hides an income page does not protect the income endpoint from a direct request. Conversely, a feature flag that makes a route return “not found” can explain why a user cannot see a capability without implying that the data has been removed.

sequenceDiagram participant C as Client participant A as API auth guard participant J as JWT strategy participant R as Request context participant G as Permission and feature guards participant S as Tenant-aware service participant D as Scoped data store C->>A: Bearer token and selected context A->>J: Validate token J->>J: Validate user, employee, and organization membership J-->>A: Authenticated user A->>R: Create context and correlation ID R->>G: Expose tenant, organization, role, and employee G->>S: Allow when policy passes S->>D: Apply tenant and employee scope D-->>C: Scoped result or rejection

The sequence has two important failure points. Identity or membership can fail before a business service runs. Policy or scope can fail after identity succeeds. A successful login therefore says less than many users assume: it proves an account, not universal access. For Gauzy’s shared operational context to be useful, every later relationship—employee to project, project to task, task to time log, or invoice to client—must be interpreted inside this sequence.

That is the foundation for the next question: once a request has a valid operating scope, how does Gauzy organize work inside it? Projects, goals, teams, sprints, and tasks are where the selected context becomes a plan for everyday execution.

References