diff --git a/.optimize-cache.json b/.optimize-cache.json index 162c4a82c6..2e61d947ba 100644 --- a/.optimize-cache.json +++ b/.optimize-cache.json @@ -191,6 +191,7 @@ "images/blog/announcing-time-helper-queries/cover.png": "0ee1d4d1edc65bf8fc3376b761b08efaffa55dd8ca84860ab3a9c34f7d78c25b", "images/blog/announcing-timestamp-overrides/cover.png": "5bfc2ba16b8ca4a82188c0f67b300ed0a7f38b4abc04b06a10ee52b2832fa65b", "images/blog/announcing-transactions-api/cover.png": "604a7721b7bf0a752460a721ffcaff10598abc5f398e7b16a8a58195c2ebf7ea", + "images/blog/announcing-user-impersonation/cover.png": "03c2b9a8aef562caf6b491ddb22626c405e64f2e6ac83525e28e6537f3f927a5", "images/blog/apply-appwrite-how/cover.png": "d23f45ced245b42c8712c021f5d2068c17aebd94fd049cb90222cb9647a41a4a", "images/blog/appwrite-1-8-0-self-hosted-release/cover.png": "c15a9d88ccd16c2dc8333dc74e715e1f4a6c7818d3b4a05f4d68342eacdc0523", "images/blog/appwrite-1-8-1-self-hosted-release/cover.png": "82f0a396c56b6b299b24133079acc6a317c66b2bf02fd91f4862bd3be0f8f373", diff --git a/src/routes/blog/post/announcing-user-impersonation/+page.markdoc b/src/routes/blog/post/announcing-user-impersonation/+page.markdoc new file mode 100644 index 0000000000..d6fdf37233 --- /dev/null +++ b/src/routes/blog/post/announcing-user-impersonation/+page.markdoc @@ -0,0 +1,287 @@ +--- +layout: post +title: 'Introducing user impersonation for Appwrite Auth' +description: Trusted operators can now act as another user in Appwrite Auth to debug issues, validate permissions, and support customers without sharing credentials. +date: 2026-03-14 +cover: /images/blog/announcing-user-impersonation/cover.png +timeToRead: 4 +author: eldad-fux +category: announcement, product +featured: false +--- + +Debugging user-specific issues is one of the hardest parts of building authentication-heavy apps. + +You get a bug report that only happens for one customer. A teammate needs to confirm whether a permission is misconfigured. Support wants to walk through the exact experience an end user is seeing. Until now, that usually meant asking for screenshots, requesting temporary credentials, or trying to recreate the entire account setup by hand. + +Appwrite now supports **user impersonation** in Auth. + +With impersonation, a trusted operator can sign in as themselves, choose a target user, and send subsequent requests using that user's permissions and access level. That makes it much easier to troubleshoot production issues, validate access rules, and review critical flows exactly as the user experiences them. + +# Simpler than previous impersonation flows + +This kind of workflow was already possible before if you were willing to build it yourself with **Server SDKs**, **SSR session handling**, or custom internal tooling. + +Teams could create operator-specific flows that acted on behalf of users, pass sessions into a server-side client, and proxy requests through backend code. That approach worked, but it also meant more plumbing around session handling, more support-only code paths, and more room for mistakes in internal tools. + +With native impersonation support, the flow becomes much simpler: + +- Mark a trusted operator as an impersonator in the Console or through the Users API +- Have them authenticate as themselves +- Set the impersonation target directly on the Appwrite client +- Use the same SDKs and product APIs you already use everywhere else + +Instead of building the entire mechanism around impersonation, you can focus on the operator experience around it. + +# What ships with this release + +This release adds the building blocks needed to support impersonation safely inside Appwrite: + +- A new `impersonator` boolean on the user model +- A dedicated endpoint to enable or disable impersonation for a user from any of the server SDKs +- Console support so trusted operators can be marked as impersonators directly from **Auth > Users** +- Automatic `users.read` scope for impersonators so they can browse project users and build internal user-pickers from either client or server side +- Three client setters for selecting the target user by ID, email, or phone +- Response models that expose `impersonatorUserId` when impersonation is active + +In practice, the flow is simple: + +1. Mark a trusted operator as an impersonator. +2. Have them sign in normally using Appwrite Auth. +3. Set one impersonation target value on the client using the new client SDK methods. +4. Appwrite resolves the target user and evaluates the request as that user. + +# Why this matters + +Impersonation removes a lot of friction from real-world support and operations work. + +Here are a few scenarios where it helps immediately: + +- **Customer support**: Reproduce account-specific issues without asking users to share passwords. +- **QA and testing**: Confirm exactly what different users can and cannot access. +- **Permissions debugging**: Validate role, team, or label-based behavior from the user's point of view. +- **Onboarding reviews**: Walk through signup, billing, or upgrade flows as a real user would see them. +- **High-touch enterprise support**: Investigate tenant-specific issues without sharing temporary logins across teams. +- **Internal release validation**: Test new rollout logic against real-world user configurations before support tickets arrive. + +Instead of rebuilding the environment from scratch or guessing what a user is seeing, your team can verify the behavior directly. + +Another practical detail: once a user is marked as an impersonator, Appwrite automatically grants them the `users.read` scope. That means your internal support or operations tools can show a full project user list, add search, and let operators choose a target user from an admin-like UI before they start impersonating. + +# How it looks in code + +Once the operator has signed in as themselves, impersonation becomes a small client configuration step. + +# By user ID + +{% multicode %} + +```client-web +import { Client, Account } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserId(''); + +const account = new Account(client); +const user = await account.get(); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserId(''); + +Account account = Account(client); +final user = await account.get(); +``` + +```client-apple +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserId("") + +let account = Account(client) +let user = try await account.get() +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserId("") + +val account = Account(client) +val user = account.get() +``` + +{% /multicode %} + +# By email + +{% multicode %} + +```client-web +import { Client, Account } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserEmail('user@example.com'); + +const account = new Account(client); +const user = await account.get(); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserEmail('user@example.com'); + +Account account = Account(client); +final user = await account.get(); +``` + +```client-apple +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserEmail("user@example.com") + +let account = Account(client) +let user = try await account.get() +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserEmail("user@example.com") + +val account = Account(client) +val user = account.get() +``` + +{% /multicode %} + +# By phone + +{% multicode %} + +```client-web +import { Client, Account } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserPhone('+12065550100'); + +const account = new Account(client); +const user = await account.get(); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserPhone('+12065550100'); + +Account account = Account(client); +final user = await account.get(); +``` + +```client-apple +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserPhone("+12065550100") + +let account = Account(client) +let user = try await account.get() +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserPhone("+12065550100") + +val account = Account(client) +val user = account.get() +``` + +{% /multicode %} + +That means support and QA teams can keep using the same Appwrite SDK patterns they already know, while switching the effective user only when needed. + +# Built for controlled use + +Impersonation is intentionally designed to work from a **real user-authenticated session**, not from requests authenticated with your **server integration** (project API key) alone. + +That means operators still authenticate as themselves first. Appwrite then uses the impersonation setting on the client only to select the effective user for the request. This keeps the flow explicit and better aligned with internal support tooling, admin panels, and controlled QA environments. + +# How logs are treated + +We also added clear behavior around auditability and visibility: + +- Users with the impersonator capability automatically receive `users.read`, so they can list project users before choosing a target +- The user model includes `impersonatorUserId` when impersonation is active +- Internal audit logs continue to attribute actions to the **original impersonator** +- Internal audit payload data also records the **impersonated target** + +This matters because support and operations teams usually need two answers at the same time: + +1. Who actually performed the action? +2. Which user experience was being impersonated? + +With this release, Appwrite keeps the real actor in internal audits while still preserving the effective target in the audit payload. That makes it much easier to investigate incidents, review support activity, and keep internal tooling accountable. + +On the app side, you can also use `impersonatorUserId` to show a persistent banner whenever someone is currently acting on behalf of another user. + +# How to get started + +To enable impersonation for an operator, you can use the Appwrite Console under **Auth > Users** or the Users API reference flow for updating the impersonator capability. + +After that, authenticated requests from that user can impersonate another user by setting one of the impersonation helpers on the Appwrite client: + +- `setImpersonateUserId()` +- `setImpersonateUserEmail()` +- `setImpersonateUserPhone()` + +If you're building an internal support dashboard, a good pattern is: + +- Let admins search for users +- Start impersonation only after an explicit action +- Show a visible banner while impersonation is active +- Make it easy to stop impersonating and return to the operator's own session + +# More resources + +- [Read the user impersonation docs](/docs/products/auth/impersonation) +- [Manage users with the Users API](/docs/products/auth/users) +- [Explore Appwrite Authentication](/docs/products/auth) diff --git a/src/routes/blog/post/best-lovable-alternative-worth-exploring/+page.markdoc b/src/routes/blog/post/best-lovable-alternative-worth-exploring/+page.markdoc new file mode 100644 index 0000000000..5d66520179 --- /dev/null +++ b/src/routes/blog/post/best-lovable-alternative-worth-exploring/+page.markdoc @@ -0,0 +1,164 @@ +--- +layout: post +title: "Best Lovable alternative worth exploring in 2026" +description: "Looking for a Lovable alternative? This guide explains where Lovable works well, where teams often outgrow it, and why Imagine is a strong option for full-stack product development." +date: 2026-03-13 +cover: /images/blog/introducing-imagine/cover.png +timeToRead: 7 +author: eldad-fux +category: product,init +featured: false +draft: false +callToAction: true +--- + +If you are evaluating alternatives to Lovable, the right answer depends less on which tool is more popular and more on what you need the product to become. + +Lovable has earned its place in the market by making app creation feel accessible. It helps teams move from idea to interface quickly, which is exactly what many early-stage projects need. For mockups, concept validation, lightweight internal tools, and fast UI exploration, that speed is valuable. + +The question is what happens next. + +Once a project moves beyond early validation, the priorities usually change. Teams start thinking about authentication, data models, storage, permissions, backend logic, deployment, and long-term maintainability. At that point, the best Lovable alternative is usually not the one that generates the nicest first screen. It is the one that reduces the distance between prototype and production. + +That is where [Imagine](https://imagine.dev/) becomes especially compelling. + +# Why teams start looking for a Lovable alternative + +Most teams do not switch because the first tool was bad. They switch because the scope of the project changed. + +A tool that feels perfect during the first week can start to feel limiting once the app needs real product infrastructure behind it. In practice, the search for a Lovable alternative often begins when teams need: + +- A workflow that includes both frontend and backend, not just interface generation +- Built-in support for auth, database, storage, and hosting +- A clearer path from prototype to production +- Less integration work across multiple services +- More confidence that what they are generating can keep evolving with the product + +This is an important distinction in the current AI app-building landscape. Some tools are optimized for fast visual output. Others are designed to help you ship something operationally real. + +# Where Lovable still makes sense + +Any credible comparison should start by acknowledging where Lovable is strong. + +Lovable is a good fit when the immediate goal is speed. It is particularly useful for founders, designers, solo builders, and non-technical teams who want to turn an idea into something visible without a lot of setup. That low-friction experience matters, especially in the earliest phase of product exploration. + +If your main objective is to test a concept, share a polished mockup, or get early feedback on a user experience direction, Lovable remains a sensible option. + +The tradeoff is that many projects do not stay in that phase for long. Once the product needs persistent data, user accounts, permissions, files, workflows, or deployment discipline, the evaluation criteria shift. + +# The Lovable alternative most worth exploring: Imagine + +[Imagine](https://imagine.dev/) stands out because it is not only focused on generating applications with AI. It is designed around a full-stack workflow from the start. + +The key difference is its built-in Appwrite integration. + +That matters because Appwrite provides the product infrastructure many teams end up needing anyway, including: + +- Authentication +- Databases +- Storage +- Hosting +- Backend logic + +This changes the role of the AI builder. Instead of producing a front-end shell and leaving the rest to be assembled later, Imagine is positioned to help teams create applications with a backend foundation already in place. + +For teams building something they expect to operate, maintain, and grow, that is a meaningful advantage. + +# Why Imagine is a stronger choice for full-stack product work + +## 1. It reduces the gap between prototype and production + +One of the most common problems with AI-generated apps is that the initial result looks promising, but the second phase becomes a manual rebuild. Teams often discover that the UI came quickly, but the actual product still needs infrastructure, integrations, and architectural decisions that were never part of the original flow. + +Imagine addresses that problem more directly because it is built with Appwrite as part of the experience. That means the app is not being created in isolation from the systems it will depend on. + +For the reader evaluating tools, this is the real question: are you creating a demo, or are you creating the beginning of a product? Imagine is more compelling when the answer is the latter. + +## 2. The backend is part of the workflow, not a separate project + +Many teams underestimate how much time is spent after the interface is generated. User management, database structure, file handling, permissions, deployment, and backend logic quickly become the real work. + +When those pieces are treated as separate follow-up tasks, the cost of the "fast start" can show up later as integration overhead and fragmentation. + +Imagine is valuable because it treats backend capabilities as first-class building blocks. For teams that want a more coherent way to build, that can reduce rework and simplify decision-making early. + +## 3. It helps limit tool sprawl + +A common pattern in modern product development is to assemble a stack from many different services: one for UI generation, one for auth, one for storage, one for data, one for hosting, and additional tooling for workflows or backend behavior. + +That approach can work, but it creates more operational surface area. It introduces more configuration, more handoffs, and more points of failure. + +Imagine is attractive because it consolidates more of that journey into a single product direction. For teams trying to move quickly without accumulating avoidable complexity, that matters. + +## 4. It is better aligned with real application requirements + +Not every project needs a full-stack foundation on day one. But many products reach that point faster than expected. + +Internal tools need access control. SaaS MVPs need user accounts and persistence. Dashboards need structured data. Client portals need storage and permissions. Customer-facing apps need reliability, hosting, and a path to iteration without constant rework. + +Imagine is better aligned with those use cases than tools whose main strength is producing attractive frontends quickly. It is built for teams that already know the app is meant to do real work. + +## 5. It is more relevant for teams thinking about long-term delivery + +As AI builders mature, the market is moving past novelty. Teams are no longer asking only whether a tool can generate an app. They are asking whether the generated app fits into a sustainable development workflow. + +That is where Imagine becomes more interesting. Its appeal is not just speed. It is that the generated output sits closer to a real delivery model, where product infrastructure, deployment, and maintainability are considered part of the outcome. + +{% call_to_action title="Build beyond the first prototype" description="Create apps with AI while keeping auth, data, storage, and hosting in the same workflow." point1="Built-in backend" point2="Built by the Appwrite team" point3="Fewer moving parts" point4="Stronger path to production" cta="Try Imagine" url="https://imagine.dev/" /%} + +# When Lovable may still be the better choice + +This comparison is more useful when it is clear that the best tool depends on the job. + +Lovable may still be the better fit when: + +- You are primarily exploring an idea and want immediate visual output +- The app does not yet need a serious backend +- You want a polished concept quickly for feedback, pitching, or validation +- Your priority is interface iteration rather than production architecture + +Those are legitimate priorities. The mistake is assuming every AI builder should be evaluated on the same axis. + +If you are still testing whether the idea deserves investment, speed to concept may matter more than backend depth. If you are already planning for users, data, workflows, and operations, the balance changes. + +# How to evaluate any Lovable alternative + +If you are comparing tools in this category, these questions are usually more useful than a long feature checklist. + +## What happens after the first version? + +Look beyond the initial output. Can the result evolve into something your team would actually want to maintain, or does the workflow create a likely rebuild later? + +## Is the backend integrated or deferred? + +If your app needs auth, storage, data, or backend logic, this matters immediately. A tool that includes those capabilities in the building experience can save time and reduce architectural drift. + +## How many services will you need to add later? + +A fast prototype can become an expensive setup if it depends on too many separate systems to become production-ready. + +## Does the tool match your stage? + +Some tools are excellent for idea validation. Others are better for building the first real version of a product. Be honest about which phase you are actually in. + +## Are you optimizing for speed alone or for continuity? + +The fastest path to a screen is not always the fastest path to a shipped product. Continuity matters, especially once a team starts building beyond the demo. + +# Final thoughts + +The best Lovable alternative is not the one that looks most similar to Lovable. It is the one that solves the next set of problems your team is likely to face. + +Lovable remains a strong option for fast UI exploration and early validation. But for teams that want an AI builder connected to a real backend foundation from the start, [Imagine](https://imagine.dev/) stands out in a more meaningful way. + +Its advantage is not only that it helps generate applications. It is that it does so within a workflow shaped around Appwrite's backend capabilities, which makes it more relevant for teams building products with real users, real data, and real operational needs. + +That is why Imagine is one of the most worthwhile Lovable alternatives to explore in 2026, especially for teams thinking beyond the prototype. + +# More resources + +- [Imagine](https://imagine.dev/) +- [Introducing Imagine: from ideas to real products](/blog/post/introducing-imagine) +- [Comparing the best vibe coding tools](/blog/post/comparing-vibe-coding-tools) +- [Appwrite Sites documentation](/docs/products/sites) +- [Appwrite Storage documentation](/docs/products/storage) \ No newline at end of file diff --git a/src/routes/changelog/(entries)/2026-03-14.markdoc b/src/routes/changelog/(entries)/2026-03-14.markdoc new file mode 100644 index 0000000000..0e07e2e564 --- /dev/null +++ b/src/routes/changelog/(entries)/2026-03-14.markdoc @@ -0,0 +1,16 @@ +--- +layout: changelog +title: 'User impersonation for Appwrite Auth' +date: 2026-03-14 +cover: /images/blog/announcing-user-impersonation/cover.png +--- + +Trusted operators can now impersonate users in Appwrite Auth to reproduce issues, validate permissions, and provide hands-on support without sharing credentials. + +This release adds a new `impersonator` capability on users, available from the Appwrite Console and the Users API, plus direct SDK support for targeting a user by ID, email, or phone. + +Impersonated requests still begin from a real authenticated user session, and internal audit logs continue to attribute actions to the original impersonator while recording the impersonated target in internal audit payload data. + +{% arrow_link href="/blog/post/announcing-user-impersonation" %} +Read the announcement to learn more +{% /arrow_link %} diff --git a/src/routes/docs/apis/rest/+page.markdoc b/src/routes/docs/apis/rest/+page.markdoc index 656c56acc0..f29673e724 100644 --- a/src/routes/docs/apis/rest/+page.markdoc +++ b/src/routes/docs/apis/rest/+page.markdoc @@ -11,34 +11,65 @@ Appwrite supports multiple protocols for accessing the server, including [REST]( Appwrite's REST APIs expect certain headers to be included with each request: {% table %} + - Header -- +- - Description + --- + - X-Appwrite-Project: [PROJECT-ID] - required - The ID of your Appwrite project + --- + - Content-Type: application/json - required - Content type of the HTTP request. Typically set to `application/json`. + --- + - X-Appwrite-Key: [API-KEY] - optional - API key used for server authentication. Your API key is a secret, **do not** use it in client applications. + --- + - X-Appwrite-JWT: [TOKEN] - optional - Token used for JWT authentication, tokens can be generated using the [Create JWT](/docs/products/auth/jwt) method. + --- + - X-Appwrite-Response-Format: [VERSION-NUMBER] - optional - Version number used for backward compatibility. The response will be formatted to be compatible with the provided version number. This helps Appwrite SDKs keep backward compatibility with Appwrite server API version. + --- + - X-Fallback-Cookies: [FALLBACK-COOKIES] - optional - Fallback cookies used in scenarios where browsers do not allow third-party cookies. Often used when there is no Custom Domain set for your Appwrite API. -{% /table %} + +--- + +- X-Appwrite-Impersonate-User-Id: [USER-ID] +- optional +- Resolves the effective user for an already authenticated impersonator request by Appwrite user ID. Only works when the authenticated user has impersonation enabled. + +--- + +- X-Appwrite-Impersonate-User-Email: [EMAIL] +- optional +- Resolves the effective user for an already authenticated impersonator request by email address. Only works when the authenticated user has impersonation enabled. + +--- + +- X-Appwrite-Impersonate-User-Phone: [PHONE] +- optional +- Resolves the effective user for an already authenticated impersonator request by phone number. Only works when the authenticated user has impersonation enabled. + {% /table %} # Authentication {% #authentication %} @@ -101,6 +132,22 @@ X-Appwrite-Project: X-Appwrite-JWT: [TOKEN] ``` +## Impersonation headers {% #impersonation-headers %} + +When a request is already authenticated as a user with impersonation enabled, you can add one of Appwrite's impersonation headers to resolve a different effective user for that request. + +Use exactly one of these headers: + +- `X-Appwrite-Impersonate-User-Id` +- `X-Appwrite-Impersonate-User-Email` +- `X-Appwrite-Impersonate-User-Phone` + +These headers are ignored for plain API key requests. They are only honored when the request already belongs to a signed-in user who has been marked as an impersonator in the Appwrite Console or through the Users API. + +If you are using an Appwrite SDK instead of raw REST calls, use the corresponding client setters rather than manually attaching these headers. + +Learn more in the [user impersonation docs](/docs/products/auth/impersonation). + # Files {% #files %} Appwrite implements resumable, chunked uploads for files larger than 5MB. Chunked uploads send files in chunks of 5MB to reduce memory footprint and increase resilience when handling large files. [Appwrite SDKs](/docs/sdks) will automatically handle chunked uploads, but it is possible to implement this with the REST API directly. @@ -108,58 +155,76 @@ Appwrite implements resumable, chunked uploads for files larger than 5MB. Chunke Upload endpoints in Appwrite, such as [Create File](/docs/references/cloud/client-web/storage#createFile) and [Create Deployment](/docs/references/cloud/server-nodejs/functions#createDeployment), are different from other endpoints. These endpoints take multipart form data instead of JSON data. To implement chunked uploads, you will need to implement the following headers. If you wish, this logic is already available in any of the [Appwrite SDKs](/docs/sdks). {% table %} + - Header -- +- - Description + --- + - X-Appwrite-Project: [PROJECT-ID] - required - The ID of your Appwrite project + --- + - Content-Type: multipart/form-data; boundary=[FORM-BOUNDARY] - required - Contains the content type of the HTTP request and provides a [boundary](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) that is used to parse the form data. + --- + - Content-Range: bytes [BYTE-RANGE] - required - Contains information about which bytes are being transmitted in this chunk, with the format `[FIRST-BYTE]-[LAST-BYTE]/[TOTAL-BYTES]`. + --- + - X-Appwrite-ID: [FILE-ID] - required - Contains ID of the file this chunk belongs to. + --- + - X-Appwrite-Key: [API-KEY] - optional - API key used for server authentication. Your API key is a secret, **do not** use it in client applications. -{% /table %} + {% /table %} The multipart form data is structured as follows: {% table %} + - Key -- +- - Value - File Name - Description + --- + - fileId - optional - [FILE-ID] - N/A - Contains the file ID of the new file. Only used by file chunks following the first chunk uploaded. + --- + - file - required - [CHUNK-DATA] - [FILE-NAME] - Contains file chunk data. + --- + - permissions - required - [PERMISSION ARRAY] - N/A - Contains an array of permission strings about who can access the new file. -{% /table %} + {% /table %} While cURL and fetch are great tools to explore other REST endpoints, it's impractical to use for chunked file uploads because you need to split files into chunks. @@ -201,60 +266,90 @@ Some use cases do not allow custom headers, such as embedding images from Appwri Appwrite SDKs have helpers to generate permission string formats, but when using Appwrite without SDKs, you'd need to create the strings yourself. {% table %} + - Query method - API string + --- + - `Permission.read()` - `read("")` + --- + - `Permission.create()` - `read("")` + --- + - `Permission.update()` - `update("")` + --- + - `Permission.delete()` - `delete("")` + --- + - `Permission.write()` - `write("")` -{% /table %} + {% /table %} ## Roles {% #roles %} Appwrite SDKs have helpers to generate roles string formats, but when using Appwrite without SDKs, you'd need to create the strings yourself. {% table %} + - Role method - API string + --- + - `Role.any()` - `any` + --- + - `Role.guests()` - `guests` + --- + - `Role.users()` - `users` + --- + - `Role.users([STATUS])` - `users/[STATUS]` + --- + - `Role.user([USER_ID])` - `user:[USER_ID]` + --- + - `Role.user([USER_ID], [STATUS])` - `user:[USER_ID]/[STATUS]` + --- + - `Role.team([TEAM_ID])` - `team:[TEAM_ID]` + --- + - `Role.team([TEAM_ID], [ROLE])` - `team:[TEAM_ID]/[ROLE]` + --- + - `Role.member([MEMBERSHIP_ID])` - `member:[MEMBERSHIP_ID]` -{% /table %} + {% /table %} # Unique ID {% #unique-id %} @@ -266,7 +361,7 @@ Appwrite's SDKs provide a `Query` class to generate JSON query strings. When using Appwrite without an SDK, you can template your own JSON strings. You can discover the query methods available in the [Queries page.](/docs/products/databases/queries) -## Query string format {% #queries-string-format %} +## Query string format {% #queries-string-format %} Appwrite Queries are escaped JSON strings, which look like this. @@ -274,10 +369,11 @@ Appwrite Queries are escaped JSON strings, which look like this. "{\"method\":\"equal\",\"column\":\"name\",\"values\":[\"John\"]}" ``` -Query strings are passed to Appwrite using the `queries` parameter. +Query strings are passed to Appwrite using the `queries` parameter. You can attach multiple query strings by including the array parameter multiple times in the query string: `queries[]="..."&queries[]="..."` For example, the unescaped query string might look like this. + ```text ?queries[0]={"method":"equal","column":"name","values":["John"]}&queries[1]={"method":"limit","values":[6]} ``` @@ -305,16 +401,13 @@ For example, to query for all rows with the name "John" or "Jane", the query str ```json { - "method": "equal", - "column": "name", - "values": [ - "John", - "Jane" - ] + "method": "equal", + "column": "name", + "values": ["John", "Jane"] } ``` -Here are some more examples of the JSON query format. +Here are some more examples of the JSON query format. When in doubt, you can use the Appwrite SDKs to generate the query strings for you. ```json @@ -338,26 +431,27 @@ When in doubt, you can use the Appwrite SDKs to generate the query strings for y ``` ## Query nesting {% #query-nesting %} -Some Appwrite query methods, like `and` and `or`, allow you to nest queries. + +Some Appwrite query methods, like `and` and `or`, allow you to nest queries. When using Appwrite without an SDK, you can template your own JSON strings. In these cases, `column` is empty and `values` is an array of queries. ```json { - "method": "and", - "values": [ - { - "method": "equal", - "column": "name", - "values": ["John"] - }, - { - "method": "between", - "column": "age", - "values": [20, 30] - } - ] + "method": "and", + "values": [ + { + "method": "equal", + "column": "name", + "values": ["John"] + }, + { + "method": "between", + "column": "age", + "values": [20, 30] + } + ] } ``` diff --git a/src/routes/docs/products/auth/+layout.svelte b/src/routes/docs/products/auth/+layout.svelte index ed0fa99b64..2b12ad7b97 100644 --- a/src/routes/docs/products/auth/+layout.svelte +++ b/src/routes/docs/products/auth/+layout.svelte @@ -36,6 +36,10 @@ label: 'Teams', href: '/docs/products/auth/teams' }, + { + label: 'Impersonation', + href: '/docs/products/auth/impersonation' + }, { label: 'Preferences', href: '/docs/products/auth/preferences' diff --git a/src/routes/docs/products/auth/+page.markdoc b/src/routes/docs/products/auth/+page.markdoc index 7197b3fb54..b0f1a5110e 100644 --- a/src/routes/docs/products/auth/+page.markdoc +++ b/src/routes/docs/products/auth/+page.markdoc @@ -13,6 +13,7 @@ Add authentication to your app in 5 minutes {% /arrow_link %} # Authentication methods {% #auth-methods %} + Appwrite supports a variety of authentication methods to fit every app and every niche. Explore Appwrite's authentication flows. {% cards %} @@ -49,9 +50,11 @@ Implementing MFA to add extra layers of security to your app. {% /cards %} # Flexible permissions {% #flexible-permissions %} + When users sign up using Appwrite, their identity is automatically attached to a robust permissions system. Appwrite Authentication provides permissions for individual users and groups of users through [teams](/docs/products/auth/teams) and [labels](/docs/products/auth/labels). # Built in preferences {% #built-in-preferences %} + Appwrite **Authentication** comes with built-in [preferences](/docs/products/auth/preferences) for users to manage their account settings. Store notification settings, themes, and other user preferences to be shared across devices. diff --git a/src/routes/docs/products/auth/impersonation/+page.markdoc b/src/routes/docs/products/auth/impersonation/+page.markdoc new file mode 100644 index 0000000000..66c457b2df --- /dev/null +++ b/src/routes/docs/products/auth/impersonation/+page.markdoc @@ -0,0 +1,289 @@ +--- +layout: article +title: User impersonation +description: Let trusted operators act as another user in Appwrite Auth for support, QA, and troubleshooting while keeping the flow controlled and auditable. +--- + +User impersonation lets a trusted user temporarily act as another user in the same Appwrite project. This is useful for support workflows, reproducing user-reported bugs, validating permissions, and checking how your app behaves from the user's point of view without sharing credentials. + +With impersonation, the operator still signs in as themselves first. Appwrite then uses one impersonation setting on the client to resolve the target user and execute the request using that user's permissions. + +{% info title="Important" %} +Impersonation only works on requests that are already authenticated as a user with impersonation enabled. `X-Appwrite-Key` alone is not enough. +{% /info %} + +# How it works {% #how-it-works %} + +Impersonation follows four steps: + +1. Enable the `impersonator` capability for a trusted operator. +2. Have that operator sign in normally using any supported Appwrite Auth flow. +3. Set exactly one impersonation target on the Appwrite client. +4. Appwrite resolves the target user and evaluates the request as that user. + +When impersonation is active, the returned user model includes `impersonatorUserId`. Your app can use this to show a visible banner or disable risky actions while someone is acting on behalf of another user. + +# Enable impersonation for an operator {% #enable-impersonation %} + +You can enable impersonation for a trusted operator in two places: + +- In the Appwrite Console under **Auth > Users**, by opening the user you want to trust and enabling their impersonator capability +- In the Users API, by updating that user's `impersonator` field + +This capability should only be granted to internal support, QA, or operations users who need to inspect app behavior from an end-user perspective. + +{% info title="Recommended setup" %} +Create a dedicated internal operator account for each team member instead of sharing one support account. This makes it easier to review internal audit activity and control access over time. +{% /info %} + +When a user is marked as an impersonator, Appwrite also grants them the `users.read` scope. This allows trusted operators to list users in the project, which is especially useful when building internal admin-style tools that let support or QA teams search for a user and choose who to impersonate next. + +{% arrow_link href="/docs/references/cloud/server-nodejs/users#updateImpersonator" %} +See the Users API reference for update impersonator +{% /arrow_link %} + +# Initialize an impersonated client {% #initialize-an-impersonated-client %} + +After the operator signs in as themselves, initialize the Appwrite client the same way you normally would, then add exactly one impersonation setter on the client. + +Use only one of them per request flow. + +{% info title="SDK support" %} +Use an Appwrite SDK version that includes impersonation support. In client-side apps, the operator's session is typically already persisted for you after login, so impersonation becomes a small client configuration step. +{% /info %} + +# By user ID {% #impersonate-by-id %} + +Impersonating by user ID is the most precise option. Use it when your internal tools already store the Appwrite user ID or when an operator selected a user from your support dashboard. + +{% multicode %} + +```client-web +import { Client, Account } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserId(''); + +const account = new Account(client); +const user = await account.get(); + +if (user.impersonatorUserId) { + console.log(`Impersonated by ${user.impersonatorUserId}`); +} +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserId(''); + +Account account = Account(client); +final user = await account.get(); +print(user.name); +``` + +```client-apple +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserId("") + +let account = Account(client) +let user = try await account.get() +print(user.name) +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserId("") + +val account = Account(client) +val user = account.get() +println(user.name) +``` + +{% /multicode %} + +# By email {% #impersonate-by-email %} + +Impersonating by email is useful in admin panels and support workflows where operators search for users by email address first. + +{% multicode %} + +```client-web +import { Client, Account } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserEmail('user@example.com'); + +const account = new Account(client); +const user = await account.get(); +console.log(user.email); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserEmail('user@example.com'); + +Account account = Account(client); +final user = await account.get(); +print(user.email); +``` + +```client-apple +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserEmail("user@example.com") + +let account = Account(client) +let user = try await account.get() +print(user.email) +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserEmail("user@example.com") + +val account = Account(client) +val user = account.get() +println(user.email) +``` + +{% /multicode %} + +# By phone {% #impersonate-by-phone %} + +Impersonating by phone is helpful for support flows where the phone number is the primary identifier or when your app is centered around SMS-based authentication. + +{% multicode %} + +```client-web +import { Client, Account } from "appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserPhone('+12065550100'); + +const account = new Account(client); +const user = await account.get(); +console.log(user.phone); +``` + +```client-flutter +import 'package:appwrite/appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setImpersonateUserPhone('+12065550100'); + +Account account = Account(client); +final user = await account.get(); +print(user.phone); +``` + +```client-apple +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserPhone("+12065550100") + +let account = Account(client) +let user = try await account.get() +print(user.phone) +``` + +```client-android-kotlin +import io.appwrite.Client +import io.appwrite.services.Account + +val client = Client(context) + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setImpersonateUserPhone("+12065550100") + +val account = Account(client) +val user = account.get() +println(user.phone) +``` + +{% /multicode %} + +# Choosing the right identifier {% #choosing-the-right-identifier %} + +Each setter resolves the same effective user context, but each one fits a different operator workflow: + +- Use **user ID** when you already have the canonical Appwrite ID +- Use **email** when support teams search by email address +- Use **phone** when your app is centered around phone login or phone-based onboarding + +Only set one impersonation value at a time. If you need to switch targets, create a fresh client or replace the previous impersonation value before continuing. + +# Build safe support tooling {% #build-safe-support-tooling %} + +Impersonation is most useful when you wrap it in explicit operator UX: + +- Use the automatically granted `users.read` scope to build a user picker or searchable support view +- Show a clear banner while impersonation is active +- Display both the operator identity and the effective user identity +- Require an explicit action to start impersonation +- Let operators stop impersonating with one click +- Limit impersonation features to internal tools and trusted roles + +# Common use cases {% #common-use-cases %} + +User impersonation is especially useful when you need to: + +- Reproduce a bug that only appears for a specific user +- Verify permissions and feature access from the user's point of view +- Help customer support teams troubleshoot account issues +- Review onboarding or upgrade flows exactly as an end user sees them + +# Security and visibility {% #security-and-visibility %} + +Keep impersonation limited to trusted operators and internal tools. + +Important behavior to know: + +- Impersonation must start from a real user session, not an API key by itself. +- Users with impersonation enabled are automatically granted the `users.read` scope. +- The target user's permissions are used for the impersonated request. +- The user model exposes `impersonator` and `impersonatorUserId` so your app can react when impersonation is active. +- Internal audit logs attribute the action to the original impersonator and include the impersonated target in internal audit payload data. + +If you build an internal admin panel, use `impersonatorUserId` to make the impersonated state obvious at all times. + +# More resources {% #more-resources %} + +- [Manage users with the Users API](/docs/products/auth/users) +- [REST API impersonation docs](/docs/apis/rest#impersonation-headers) +- [Users API reference](/docs/references/cloud/server-nodejs/users) diff --git a/src/routes/docs/products/auth/users/+page.markdoc b/src/routes/docs/products/auth/users/+page.markdoc index c71865219c..b5b1eca70b 100644 --- a/src/routes/docs/products/auth/users/+page.markdoc +++ b/src/routes/docs/products/auth/users/+page.markdoc @@ -5,13 +5,15 @@ description: Manage user identities and profiles effectively with Appwrite. Dive --- Appwrite Users API is used for managing users in server applications. -Users API can only be used with an API key with the [Server SDK](/docs/sdks#server), to manage all users. +Users API can only be used with an API key with the [Server SDK](/docs/sdks#server), to manage all users. If you need to act on behalf of users through an Appwrite Function or your own backend, use [JWT login](/docs/products/auth/jwt). +Need to troubleshoot from a user's point of view? Use [user impersonation](/docs/products/auth/impersonation) to let trusted operators temporarily act as another user without sharing credentials. + {% partial file="account-vs-user.md" /%} The users API can be used to create users, import users, update user info, get user audit logs, and remove users. {% arrow_link href="/docs/references/cloud/server-nodejs/users" %} -Learn more in the Users API references +Learn more in the Users API references {% /arrow_link %} diff --git a/static/images/blog/announcing-user-impersonation/cover.png b/static/images/blog/announcing-user-impersonation/cover.png new file mode 100644 index 0000000000..076d354c60 Binary files /dev/null and b/static/images/blog/announcing-user-impersonation/cover.png differ