-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: xmpp integration #3
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
Conversation
- Added a new client for interacting with the Prosody REST API, supporting account creation, deletion, and existence checks. - Introduced configuration management for Prosody API credentials and endpoints. - Implemented utility functions for XMPP username validation and JID formatting. - Defined TypeScript types for account management and API responses.
- Introduced a new route for XMPP with path "/app/xmpp" and associated icon. - Updated navigation order for the admin route to accommodate the new XMPP route. - Configured metadata to prevent indexing by search engines.
- Introduced a new schema for XMPP accounts, including fields for user ID, JID, username, status, and timestamps. - Added an enum for XMPP account status with values: active, suspended, and deleted. - Updated the main schema index to include the new xmppAccount schema.
- Added 'xmpp' to the supported scopes in the OAuth provider configuration. - Implemented custom user info claims to include XMPP username when the 'xmpp' scope is requested, querying the database for the associated username.
- Introduced a new module for XMPP API client functions, including methods for fetching, creating, updating, and deleting XMPP accounts. - Added query keys for XMPP account management to facilitate data retrieval. - Updated the main API index to export the new XMPP module.
- Added custom hooks for managing XMPP accounts, including fetching, creating, updating, and deleting accounts. - Integrated TanStack Query for efficient data fetching and state management. - Implemented cache invalidation strategies to ensure data consistency after mutations.
- Introduced a new component for managing XMPP accounts, allowing users to create and delete accounts. - Integrated loading and error states for improved user experience. - Displayed account information, including JID, username, and status, with options to copy JID to clipboard. - Implemented user guidance for connecting to XMPP clients.
- Added API routes for managing XMPP accounts, including creation, retrieval, updating, and deletion. - Implemented authentication and authorization checks to ensure secure access to account information. - Handled username validation and uniqueness checks against both the database and Prosody server. - Included error handling for various scenarios, such as account existence and invalid input formats.
- Added a new page for managing XMPP accounts, including session verification and data prefetching for server-side rendering. - Integrated the XmppAccountManagement component and PageHeader for a cohesive user interface. - Implemented hydration for efficient state management with TanStack Query.
- Introduced a new script for registering the Prosody XMPP server as an OAuth client in Better Auth. - Supports confidential client creation with password grant type for legacy XMPP client compatibility. - Includes environment variable configuration and outputs necessary credentials for Prosody setup.
- Changed the default formatter for Markdown files from 'yzhang.markdown-all-in-one' to 'DavidAnson.vscode-markdownlint' for improved linting and formatting capabilities.
Reviewer's GuideAdds full XMPP integration: OAuth scope and userinfo claim, DB schema and API for XMPP accounts backed by Prosody REST, React Query client/hooks and dashboard UI for users to self-manage their XMPP accounts, route wiring, plus a script to register Prosody as an OAuth client. Sequence diagram for creating an XMPP accountsequenceDiagram
actor User
participant Browser
participant XmppAccountManagement
participant ReactQuery_useCreateXmppAccount
participant XmppApi_createXmppAccount
participant Api_POST_api_xmpp_accounts
participant Auth_requireAuth
participant DB
participant ProsodyRestClient
participant ProsodyServer
User->>Browser: Click_Create_XMPP_Account
Browser->>XmppAccountManagement: handleCreate(username)
XmppAccountManagement->>ReactQuery_useCreateXmppAccount: mutateAsync(username)
ReactQuery_useCreateXmppAccount->>XmppApi_createXmppAccount: createXmppAccount(requestBody)
XmppApi_createXmppAccount->>Api_POST_api_xmpp_accounts: HTTP_POST_/api/xmpp/accounts
Api_POST_api_xmpp_accounts->>Auth_requireAuth: requireAuth(request)
Auth_requireAuth-->>Api_POST_api_xmpp_accounts: userId
Api_POST_api_xmpp_accounts->>DB: query_xmppAccount_by_userId
DB-->>Api_POST_api_xmpp_accounts: existingAccount_or_null
alt user_already_has_account
Api_POST_api_xmpp_accounts-->>XmppApi_createXmppAccount: 409_Username_already_has_XMPP_account
XmppApi_createXmppAccount-->>ReactQuery_useCreateXmppAccount: throw_Error
ReactQuery_useCreateXmppAccount-->>XmppAccountManagement: onError
XmppAccountManagement-->>Browser: Show_error_toast
else no_existing_account
Api_POST_api_xmpp_accounts->>DB: select_user_email_by_userId
DB-->>Api_POST_api_xmpp_accounts: user_email
Api_POST_api_xmpp_accounts->>Api_POST_api_xmpp_accounts: determine_or_validate_username
Api_POST_api_xmpp_accounts->>DB: query_xmppAccount_by_username
DB-->>Api_POST_api_xmpp_accounts: existingUsername_or_null
Api_POST_api_xmpp_accounts->>ProsodyRestClient: checkProsodyAccountExists(username)
ProsodyRestClient->>ProsodyServer: REST_GET_/accounts/username
ProsodyServer-->>ProsodyRestClient: exists_or_404
ProsodyRestClient-->>Api_POST_api_xmpp_accounts: boolean_exists
Api_POST_api_xmpp_accounts->>ProsodyRestClient: createProsodyAccount(username)
ProsodyRestClient->>ProsodyServer: REST_POST_/accounts_with_XML_stanza
ProsodyServer-->>ProsodyRestClient: success
ProsodyRestClient-->>Api_POST_api_xmpp_accounts: ProsodyRestAccountResponse
Api_POST_api_xmpp_accounts->>DB: insert_xmppAccount_record
DB-->>Api_POST_api_xmpp_accounts: newAccount
Api_POST_api_xmpp_accounts-->>XmppApi_createXmppAccount: 201_ok_with_account
XmppApi_createXmppAccount-->>ReactQuery_useCreateXmppAccount: XmppAccount
ReactQuery_useCreateXmppAccount->>ReactQuery_useCreateXmppAccount: invalidateQueries_xmppAccounts_current
ReactQuery_useCreateXmppAccount-->>XmppAccountManagement: onSuccess(account)
XmppAccountManagement-->>Browser: Show_success_toast_and_clear_username
end
Browser-->>User: Render_account_details_view
Sequence diagram for deleting an XMPP accountsequenceDiagram
actor User
participant Browser
participant XmppAccountManagement
participant ReactQuery_useDeleteXmppAccount
participant XmppApi_deleteXmppAccount
participant Api_DELETE_api_xmpp_accounts_id
participant Auth_requireAuth
participant DB
participant ProsodyRestClient
participant ProsodyServer
User->>Browser: Confirm_delete_in_dialog
Browser->>XmppAccountManagement: handleDelete(account.id)
XmppAccountManagement->>ReactQuery_useDeleteXmppAccount: mutateAsync(accountId)
ReactQuery_useDeleteXmppAccount->>XmppApi_deleteXmppAccount: deleteXmppAccount(id)
XmppApi_deleteXmppAccount->>Api_DELETE_api_xmpp_accounts_id: HTTP_DELETE_/api/xmpp/accounts/id
Api_DELETE_api_xmpp_accounts_id->>Auth_requireAuth: requireAuth(request)
Auth_requireAuth-->>Api_DELETE_api_xmpp_accounts_id: userId
Api_DELETE_api_xmpp_accounts_id->>DB: query_xmppAccount_by_id
DB-->>Api_DELETE_api_xmpp_accounts_id: account_or_null
alt account_not_found
Api_DELETE_api_xmpp_accounts_id-->>XmppApi_deleteXmppAccount: 404_not_found
XmppApi_deleteXmppAccount-->>ReactQuery_useDeleteXmppAccount: throw_Error
ReactQuery_useDeleteXmppAccount-->>XmppAccountManagement: onError
XmppAccountManagement-->>Browser: Show_error_toast
else account_found
Api_DELETE_api_xmpp_accounts_id->>Api_DELETE_api_xmpp_accounts_id: check_owner_or_admin
Api_DELETE_api_xmpp_accounts_id->>ProsodyRestClient: deleteProsodyAccount(username)
ProsodyRestClient->>ProsodyServer: REST_DELETE_/accounts/username_with_XML_stanza
ProsodyServer-->>ProsodyRestClient: success_or_404
ProsodyRestClient-->>Api_DELETE_api_xmpp_accounts_id: ProsodyRestAccountResponse
Api_DELETE_api_xmpp_accounts_id->>DB: update_xmppAccount_status_to_deleted
DB-->>Api_DELETE_api_xmpp_accounts_id: deletedAccount
Api_DELETE_api_xmpp_accounts_id-->>XmppApi_deleteXmppAccount: 200_ok
XmppApi_deleteXmppAccount-->>ReactQuery_useDeleteXmppAccount: void
ReactQuery_useDeleteXmppAccount->>ReactQuery_useDeleteXmppAccount: invalidateQueries_xmppAccounts_all
ReactQuery_useDeleteXmppAccount-->>XmppAccountManagement: onSuccess
XmppAccountManagement-->>Browser: Show_success_toast
end
Browser-->>User: Render_account_creation_form_state
ER diagram for new xmppAccount table and relation to usererDiagram
user {
text id PK
text email
text name
}
xmpp_account {
text id PK
text user_id FK
text jid
text username
enum status
timestamp created_at
timestamp updated_at
jsonb metadata
}
user ||--o| xmpp_account : has_one
Class diagram for XMPP TypeScript typesclassDiagram
class XmppAccount {
+string id
+string userId
+string jid
+string username
+XmppAccountStatus status
+Date createdAt
+Date updatedAt
+Record~string,unknown~ metadata
}
class XmppAccountStatus {
<<enumeration>>
active
suspended
deleted
}
class CreateXmppAccountRequest {
+string username
}
class UpdateXmppAccountRequest {
+string username
+XmppAccountStatus status
+Record~string,unknown~ metadata
}
class ProsodyRestError {
+string error
+string message
}
class ProsodyRestAccountResponse {
+boolean success
+string error
+string message
}
XmppAccount --> XmppAccountStatus
CreateXmppAccountRequest --> XmppAccountStatus
UpdateXmppAccountRequest --> XmppAccountStatus
ProsodyRestAccountResponse --> ProsodyRestError
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey - I've found 4 issues, and left some high level feedback:
- In the XMPP account route handlers under
src/app/api/xmpp/accounts/[id]/route.ts, theparamsargument is typed and used as aPromise({ params }: { params: Promise<{ id: string }> }withawait params), but Next.js app router passesparamsas a plain object; this should be updated to{ params }: { params: { id: string } }and used synchronously. - The XMPP client helper
prosodyRequestinsrc/lib/xmpp/client.tsalways sendsContent-Type: application/xml, including for GET requests; if Prosody’s REST endpoint is strict about content types on non-body requests, you may want to only set this header when a body is present to avoid unexpected 4xx responses.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In the XMPP account route handlers under `src/app/api/xmpp/accounts/[id]/route.ts`, the `params` argument is typed and used as a `Promise` (`{ params }: { params: Promise<{ id: string }> }` with `await params`), but Next.js app router passes `params` as a plain object; this should be updated to `{ params }: { params: { id: string } }` and used synchronously.
- The XMPP client helper `prosodyRequest` in `src/lib/xmpp/client.ts` always sends `Content-Type: application/xml`, including for GET requests; if Prosody’s REST endpoint is strict about content types on non-body requests, you may want to only set this header when a body is present to avoid unexpected 4xx responses.
## Individual Comments
### Comment 1
<location> `src/app/(dashboard)/app/xmpp/page.tsx:32-34` </location>
<code_context>
+
+ // Prefetch XMPP account data for SSR
+ // This reduces loading flash by populating the cache before components mount
+ await queryClient.prefetchQuery({
+ queryKey: queryKeys.xmppAccounts.current(),
+ queryFn: async () => {
+ // We'll fetch on the client side since this is a user-specific endpoint
+ // that requires authentication cookies
</code_context>
<issue_to_address>
**issue (bug_risk):** Prefetching `xmppAccounts.current` with `null` will hydrate the query as successful and block the real fetch for 30s.
Because `useXmppAccount` configures `staleTime: 30_000`, hydrating this query with `null` marks it as `status: 'success'` with `data: null` and prevents a refetch from `/api/xmpp/accounts` for 30s. Users who already have an account will see the "no account" UI during that period. Please avoid hydrating with `null` here (e.g. remove this prefetch, use a different key for a placeholder, or change the behavior to `initialData`/`staleTime: 0` rather than simulating a real fetch).
</issue_to_address>
### Comment 2
<location> `src/app/api/xmpp/accounts/[id]/route.ts:19-21` </location>
<code_context>
+ * GET /api/xmpp/accounts/[id]
+ * Get specific XMPP account details (admin or owner only)
+ */
+export async function GET(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
</code_context>
<issue_to_address>
**suggestion:** The route handler types `params` as a Promise and awaits it, which doesn’t match Next.js route handler conventions.
In route handlers, `params` should be a plain object, not a Promise. Typing it as `Promise<{ id: string }>` and using `await params` is misleading and may confuse tooling and readers. Please change the signature to `{ params }: { params: { id: string } }` and remove the `await` in GET/PATCH/DELETE.
Suggested implementation:
```typescript
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const { userId } = await requireAuth(request);
const { id } = params;
```
You should make the same change to the `PATCH` and `DELETE` handlers in this file:
1. Change their signatures from `{ params }: { params: Promise<{ id: string }> }` to `{ params }: { params: { id: string } }`.
2. Replace any `const { id } = await params;` with `const { id } = params;`.
If there are any helper functions or tests that were updated to treat `params` as a Promise, update them to pass/use a plain object instead.
</issue_to_address>
### Comment 3
<location> `src/components/xmpp/xmpp-account-management.tsx:151-160` </location>
<code_context>
+ <code className="rounded-md bg-muted px-2 py-1 text-sm">
+ {account.jid}
+ </code>
+ <Button
+ onClick={() => {
+ navigator.clipboard.writeText(account.jid);
+ toast.success("Copied", {
+ description: "JID copied to clipboard",
</code_context>
<issue_to_address>
**suggestion:** Using `navigator.clipboard` directly may fail in non-secure contexts or older browsers without user feedback.
`navigator.clipboard.writeText` can throw or be unavailable (e.g. non-HTTPS, older browsers), which would silently fail here. Consider checking `navigator.clipboard?.writeText` first and handling failures (e.g. show an error toast on absence/rejection) instead of always showing success.
</issue_to_address>
### Comment 4
<location> `src/lib/xmpp/client.ts:27` </location>
<code_context>
+/**
+ * Make a request to Prosody REST API
+ */
+async function prosodyRequest<T>(
+ endpoint: string,
+ options: RequestInit = {}
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring this Prosody client module to separate transport, error handling, and XML construction so each concern is handled by a focused helper.
You can simplify this module by separating concerns a bit more while keeping behavior identical.
### 1. Split transport into XML/JSON helpers instead of a generic `T`
Right now `prosodyRequest<T>` guesses at the response type and does `text as unknown as T`, which makes call sites lie about what they really get.
You can centralize the fetch + error handling, then provide two focused helpers:
```ts
function createAuthHeader(): string {
const { username, password } = xmppConfig.prosody;
const credentials = Buffer.from(`${username}:${password}`).toString("base64");
return `Basic ${credentials}`;
}
async function prosodyFetch(endpoint: string, options: RequestInit = {}): Promise<Response> {
const { restUrl } = xmppConfig.prosody;
const url = `${restUrl}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
"Content-Type": "application/xml",
Authorization: createAuthHeader(),
...options.headers,
},
});
if (!response.ok) {
let errorMessage = `Prosody REST API error: ${response.status} ${response.statusText}`;
try {
const errorData = (await response.json()) as ProsodyRestError;
errorMessage = errorData.error ?? errorData.message ?? errorMessage;
} catch {
// non‑JSON error, keep default message
}
throw new Error(errorMessage);
}
return response;
}
async function prosodyXmlRequest(endpoint: string, options: RequestInit = {}): Promise<string> {
const response = await prosodyFetch(endpoint, options);
return response.text();
}
async function prosodyJsonRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const response = await prosodyFetch(endpoint, options);
return (await response.json()) as T;
}
```
Then your current usages can be explicit about what they expect:
```ts
const result = await prosodyJsonRequest<ProsodyRestAccountResponse>("/accounts", {
method: "POST",
body: stanza,
});
```
No more `text as unknown as T` and no need to inspect `content-type` at every call site.
### 2. Centralize error interpretation instead of repeating string checks
You repeat `"exists"`, `"409"`, `"conflict"`, `"not found"`, `"404"` string matching in three functions. That can move to a single helper, making call sites easier to read and safer to extend:
```ts
type ProsodyDomainError =
| "AccountAlreadyExists"
| "AccountNotFound";
function interpretProsodyError(error: unknown): ProsodyDomainError | null {
if (!(error instanceof Error)) return null;
const msg = error.message.toLowerCase();
if (msg.includes("exists") || msg.includes("409") || msg.includes("conflict")) {
return "AccountAlreadyExists";
}
if (msg.includes("not found") || msg.includes("404")) {
return "AccountNotFound";
}
return null;
}
```
Usage in `createProsodyAccount`:
```ts
try {
const result = await prosodyJsonRequest<ProsodyRestAccountResponse>("/accounts", {
method: "POST",
body: stanza,
});
return result;
} catch (error) {
const domainError = interpretProsodyError(error);
if (domainError === "AccountAlreadyExists") {
throw new Error(`XMPP account already exists: ${jid}`);
}
throw error;
}
```
Usage in `deleteProsodyAccount`:
```ts
try {
const result = await prosodyJsonRequest<ProsodyRestAccountResponse>(
`/accounts/${encodeURIComponent(username)}`,
{ method: "DELETE", body: stanza },
);
return result;
} catch (error) {
const domainError = interpretProsodyError(error);
if (domainError === "AccountNotFound") {
return { success: true };
}
throw error;
}
```
Usage in `checkProsodyAccountExists`:
```ts
try {
await prosodyXmlRequest(`/accounts/${encodeURIComponent(username)}`, { method: "GET" });
return true;
} catch (error) {
const domainError = interpretProsodyError(error);
if (domainError === "AccountNotFound") {
return false;
}
throw error;
}
```
This keeps existing semantics but removes duplicated string inspection.
### 3. Extract XML stanza builders
Inline multi-line XML makes the functions noisy and duplicates boilerplate. Small helpers keep protocol details in one place:
```ts
function buildCreateAccountStanza(username: string): string {
return `<?xml version="1.0"?>
<iq type="set" to="${xmppConfig.domain}" id="create_${Date.now()}">
<query xmlns="jabber:iq:register">
<username>${username}</username>
</query>
</iq>`;
}
function buildDeleteAccountStanza(): string {
return `<?xml version="1.0"?>
<iq type="set" to="${xmppConfig.domain}" id="delete_${Date.now()}">
<query xmlns="jabber:iq:register">
<remove/>
</query>
</iq>`;
}
```
Then the high-level functions become easier to scan:
```ts
export async function createProsodyAccount(username: string): Promise<ProsodyRestAccountResponse> {
const jid = formatJid(username, xmppConfig.domain);
const stanza = buildCreateAccountStanza(username);
try {
return await prosodyJsonRequest<ProsodyRestAccountResponse>("/accounts", {
method: "POST",
body: stanza,
});
} catch (error) {
const domainError = interpretProsodyError(error);
if (domainError === "AccountAlreadyExists") {
throw new Error(`XMPP account already exists: ${jid}`);
}
throw error;
}
}
```
Overall, these changes keep all behavior but:
- remove the unsafe generic `prosodyRequest<T>` pattern,
- consolidate error mapping in one place, and
- separate XML construction from transport logic, reducing duplication and cognitive load.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| // Note: Changing username in XMPP typically requires recreating the account | ||
| // This is a limitation of XMPP - usernames are typically immutable | ||
| // For now, we'll update the database but note that Prosody may need manual intervention | ||
| updates.username = newUsername; | ||
| updates.jid = formatJid(newUsername, xmppConfig.domain); | ||
| } |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds full XMPP support: DB schema, Prosody REST client/config, server API routes, client API/hooks/components, OAuth scope/claims, environment keys and provisioning script; plus a large rework of observability surface (removals, consolidations, and new helpers/utils). Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client App
participant API as API Route<br/>/api/xmpp/accounts
participant DB as Database
participant Prosody as Prosody Server
Client->>API: POST /api/xmpp/accounts (username?)
API->>API: Verify auth & ownership
API->>DB: Check existing xmpp_account
API->>API: Validate or generate username
API->>Prosody: POST /accounts (XML IQ create)
Prosody-->>API: Created / Conflict
API->>DB: INSERT xmpp_account (jid, username, status)
DB-->>API: Record saved
API-->>Client: 201 Created (XmppAccount)
sequenceDiagram
participant Client as Client App
participant API as API Route<br/>/api/xmpp/accounts/[id]
participant Prosody as Prosody Server
participant DB as Database
Client->>API: DELETE /api/xmpp/accounts/{id}
API->>API: Verify auth & ownership/admin
API->>Prosody: DELETE /accounts/{username} (tolerant of 404)
Prosody-->>API: Success or 404
API->>DB: UPDATE xmpp_account SET status='deleted', updatedAt=now()
DB-->>API: Soft-delete applied
API-->>Client: 200 OK / Success
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (30)
✏️ Tip: You can disable this entire section by setting Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🤖 Fix all issues with AI agents
In `@scripts/create-prosody-oauth-client.ts`:
- Around line 39-47: The code prints sensitive data for existing clients: when
existingClient is non-empty the block logs client.clientSecret; remove or mask
the secret rather than printing it. Update the early-return branch that uses
existingClient[0] (variable client) to either omit client.clientSecret entirely
or replace it with a masked placeholder (e.g., "(redacted)" or show only last 4
chars) before logging, keeping other fields (clientId, name, disabled)
unchanged. Ensure no plaintext secret is written to console in the function that
handles existing clients.
In `@src/app/api/xmpp/accounts/`[id]/route.ts:
- Around line 225-234: Replace brittle string matching in the DELETE handler
with a concrete error type from the Prosody client: export a custom
ProsodyAccountNotFoundError (or add an error.code) from deleteProsodyAccount in
client.ts and throw that when the account isn’t present; then in the catch block
of the route handler check if error instanceof ProsodyAccountNotFoundError (or
compare error.code) and only wrap/throw an APIError for other error types,
including a safe error.message fallback for non-Error values.
- Around line 132-137: Detect and block username changes instead of updating the
DB directly: in the route handler where you build `updates` (the block that sets
`updates.username` and `updates.jid` using `formatJid`), compare `newUsername`
to the existing account's username and if they differ return an HTTP 400/422
with a clear error like "username cannot be changed via API; recreate account or
contact admin", and do not set `updates.username` or `updates.jid`; leave
Prosody synchronization for a separate flow (or add a dedicated rename endpoint
that handles Prosody delete/create).
- Line 77: The request body in route handler is directly cast using "as
UpdateXmppAccountRequest" (const body = (await request.json()) as
UpdateXmppAccountRequest) with no runtime validation; replace the direct cast
with Zod validation by parsing the raw JSON via the corresponding Zod schema
(e.g., UpdateXmppAccountSchema) using parse/parseAsync or safeParse and handle
validation errors (return 400 with error details) before using "body" in the
rest of the handler; update references in this file (route.ts) to use the
validated result instead of the unchecked cast.
In `@src/app/api/xmpp/accounts/route.ts`:
- Around line 90-109: The uniqueness checks using db.query.xmppAccount.findFirst
and checkProsodyAccountExists have a TOCTOU race: two concurrent requests can
pass both checks before creation; fix by performing the account creation inside
a database transaction (use the same transaction for any DB inserts/queries)
relying on a UNIQUE constraint on xmppAccount.username as the primary guard, and
handle the constraint violation error from the transactional insert to return a
409 response; also treat Prosody creation (createProsodyAccount) as
authoritative where possible—attempt Prosody create first or roll back DB insert
on Prosody conflict—and consolidate error handling currently at the rollback
path to detect DB unique-violation and Prosody "already exists" errors and
return a consistent "Username already taken" 409 response.
In `@src/lib/auth/config.ts`:
- Around line 281-297: The db query inside customUserInfoClaims can throw and
has no error handling; wrap the call to db.query.xmppAccount.findFirst in a
try/catch, call Sentry.captureException(err) (and optionally add context like {
function: "customUserInfoClaims", userId: user.id }) in the catch, and ensure
you do not rethrow so the function returns the existing claims even if the query
fails; update the customUserInfoClaims implementation and related error path
accordingly.
In `@src/lib/xmpp/client.ts`:
- Around line 83-88: The XML stanza construction directly interpolates username
into the template string (variable stanza) which risks malformed XML; implement
or reuse an XML-escaping helper (e.g., escapeXml that replaces & < > " ' with
entities) and apply it to username before inserting into the stanza (keep
validating with isValidXmppUsername but add defense-in-depth), ensuring the
escaped value is used where stanza is built (still using xmppConfig.domain and
the existing id creation).
In `@src/lib/xmpp/config.ts`:
- Around line 41-43: The check for xmppConfig.prosody.restUrl is unreachable
because a default of "http://localhost:5281/rest" is assigned earlier; either
remove that default so the validation (if (!xmppConfig.prosody.restUrl) ...) can
enforce an explicit PROSODY_REST_URL, or delete the unreachable validation block
entirely; locate the default assignment for xmppConfig.prosody.restUrl and
remove it to require explicit config, or remove the throw/if block to accept the
default, and update any related docs/tests to reflect the chosen behavior.
In `@src/lib/xmpp/utils.ts`:
- Around line 57-60: Sanitization can leave multiple invalid leading characters
because XMPP_USERNAME_START_REGEX is applied only once; update the sanitization
to remove all leading invalid chars from localpart (reference: variable
sanitized, symbols localpart, XMPP_USERNAME_SANITIZE_REGEX,
XMPP_USERNAME_START_REGEX, XMPP_USERNAME_MAX_LENGTH). Fix by making the "start"
replace greedy (ensure XMPP_USERNAME_START_REGEX matches one-or-more leading
invalid chars, e.g. anchored with +) or loop/remove repeatedly until no leading
invalid char remains, then apply the length slice; adjust
XMPP_USERNAME_START_REGEX or the replace logic accordingly so sanitized never
starts with invalid characters.
🧹 Nitpick comments (13)
scripts/create-prosody-oauth-client.ts (1)
56-72: Consider adding timestamps and note password grant deprecation.Two observations:
The
createdAtandupdatedAtfields from the schema are not being set. Consider adding them for audit purposes.The
passwordgrant type is deprecated in OAuth 2.1. While it may be necessary for legacy XMPP client support (as documented), ensure this is intentional and document any migration path.Suggested change to add timestamps
const [newClient] = await db .insert(oauthClient) .values({ id: randomUUID(), clientId, clientSecret, name: clientName, redirectUris: [], // Not needed for server-to-server auth grantTypes: ["authorization_code", "password"], // Password grant for legacy clients tokenEndpointAuthMethod: "client_secret_post", // Prosody will use POST for auth scopes: ["openid", "xmpp"], // Required scopes skipConsent: true, // Trusted first-party client public: false, // Confidential client disabled: false, + createdAt: new Date(), + updatedAt: new Date(), }) .returning();src/lib/db/schema/xmpp.ts (1)
37-42: Redundant indexes on unique columns.The
jidandusernamecolumns already haveunique()constraints, which implicitly create indexes in PostgreSQL. The explicit indexes on lines 39-40 are redundant and add minor overhead during writes.♻️ Suggested fix
(table) => [ index("xmpp_account_userId_idx").on(table.userId), - index("xmpp_account_jid_idx").on(table.jid), - index("xmpp_account_username_idx").on(table.username), index("xmpp_account_status_idx").on(table.status), ]src/hooks/use-xmpp-account.ts (1)
49-106: Consider addingonErrorhandlers for error observability.The mutation hooks work correctly but lack
onErrorhandlers. Per coding guidelines, consider capturing exceptions with Sentry for better error observability in production.♻️ Example for useCreateXmppAccount
+import * as Sentry from "@sentry/nextjs" export function useCreateXmppAccount() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateXmppAccountRequest) => createXmppAccount(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.xmppAccounts.current(), }); }, + onError: (error) => { + Sentry.captureException(error) + }, }); }Apply the same pattern to
useUpdateXmppAccountanduseDeleteXmppAccount.src/components/xmpp/xmpp-account-management.tsx (4)
43-60: Missing Sentry error capture inhandleCreate.Per coding guidelines, exceptions should be captured using
Sentry.captureException(error)in catch blocks. The error is only shown via toast but not reported to Sentry for monitoring.♻️ Suggested fix
+'use client' + +import * as Sentry from '@sentry/nextjs' import { useState } from "react"; // ... other imports const handleCreate = async () => { try { await createMutation.mutateAsync({ username: username.trim() || undefined, }); toast.success("XMPP account created", { description: "Your XMPP account has been created successfully.", }); setUsername(""); } catch (error) { + Sentry.captureException(error); toast.error("Failed to create account", { description: error instanceof Error ? error.message : "An error occurred while creating your XMPP account.", }); } };
62-80: Missing Sentry error capture inhandleDelete.Same issue as
handleCreate- errors should be captured in Sentry for monitoring.♻️ Suggested fix
const handleDelete = async () => { if (!account) { return; } try { await deleteMutation.mutateAsync(account.id); toast.success("XMPP account deleted", { description: "Your XMPP account has been deleted successfully.", }); } catch (error) { + Sentry.captureException(error); toast.error("Failed to delete account", { description: error instanceof Error ? error.message : "An error occurred while deleting your XMPP account.", }); } };
191-202: Clipboard API lacks error handling.
navigator.clipboard.writeTextcan fail (e.g., non-secure context, permission denied). Consider wrapping in try-catch to prevent unhandled promise rejection.♻️ Suggested fix
<Button onClick={() => { - navigator.clipboard.writeText(account.jid); - toast.success("Copied", { - description: "JID copied to clipboard", - }); + navigator.clipboard.writeText(account.jid) + .then(() => { + toast.success("Copied", { + description: "JID copied to clipboard", + }); + }) + .catch(() => { + toast.error("Failed to copy", { + description: "Could not copy JID to clipboard", + }); + }); }} size="sm" variant="ghost" > Copy </Button>
228-233: Consider locale-aware date formatting.Using
toLocaleDateString()without explicit locale may produce inconsistent results. Per coding guidelines, this project handles i18n via next-intl - consider using a consistent date formatting approach.src/app/(dashboard)/app/xmpp/page.tsx (1)
30-39: Prefetching withnullprovides no SSR benefit.The comment explains the rationale, but prefetching with a
queryFnthat returnsnulldoesn't actually reduce loading flash - the client will still need to fetch the data. This adds complexity without benefit.Consider either:
- Removing the prefetch entirely (simpler)
- Implementing server-side fetching if the endpoint can be called server-side
♻️ Option 1: Remove unnecessary prefetch
export default async function XmppPage() { // Verify user session await verifySession(); - // Create QueryClient for this request (isolated per request) - const queryClient = getServerQueryClient(); - - // Prefetch XMPP account data for SSR - // This reduces loading flash by populating the cache before components mount - await queryClient.prefetchQuery({ - queryKey: queryKeys.xmppAccounts.current(), - queryFn: async () => { - // We'll fetch on the client side since this is a user-specific endpoint - // that requires authentication cookies - return null; - }, - }); const resolver = await getServerRouteResolver(); return ( - <HydrationBoundary state={dehydrate(queryClient)}> <div className="flex flex-1 flex-col gap-4 p-4 pt-0"> <div className="space-y-6"> <PageHeader pathname="/app/xmpp" resolver={resolver} /> <XmppAccountManagement /> </div> </div> - </HydrationBoundary> ); }src/app/api/xmpp/accounts/route.ts (2)
144-152: Consider logging rollback failures.When the DB insert fails after Prosody account creation, the rollback error is silently swallowed. This could leave orphaned accounts in Prosody with no visibility. Consider logging the rollback error for operational awareness.
♻️ Suggested improvement
if (!newAccount) { // Rollback: Try to delete Prosody account if database insert fails try { await deleteProsodyAccount(username); - } catch { - // Ignore rollback errors + } catch (rollbackError) { + // Log rollback failure for operational visibility - orphaned Prosody account may exist + console.error(`Failed to rollback Prosody account for username ${username}:`, rollbackError); } throw new APIError("Failed to create XMPP account record", 500); }
31-31: Missing input validation with Zod.Per coding guidelines, inputs should be validated with Zod schemas. The request body is cast directly without validation, which could lead to unexpected behavior with malformed input.
♻️ Suggested fix
+import { z } from "zod"; + +const createXmppAccountSchema = z.object({ + username: z.string().optional(), +}); + export async function POST(request: NextRequest) { try { const { userId } = await requireAuth(request); - const body = (await request.json()) as CreateXmppAccountRequest; + const parseResult = createXmppAccountSchema.safeParse(await request.json()); + if (!parseResult.success) { + return Response.json( + { ok: false, error: "Invalid request body" }, + { status: 400 } + ); + } + const body = parseResult.data;src/lib/xmpp/client.ts (1)
34-41: Missing timeout for external API calls.The fetch to Prosody REST API has no timeout configured. Per coding guidelines, external calls should have timeouts to prevent blocking indefinitely if Prosody is unresponsive.
♻️ Suggested fix with AbortController
+const PROSODY_REQUEST_TIMEOUT_MS = 10000; // 10 seconds async function prosodyRequest<T>( endpoint: string, options: RequestInit = {} ): Promise<T> { const { restUrl } = xmppConfig.prosody; const url = `${restUrl}${endpoint}`; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), PROSODY_REQUEST_TIMEOUT_MS); + - const response = await fetch(url, { - ...options, - headers: { - "Content-Type": "application/xml", - Authorization: createAuthHeader(), - ...options.headers, - }, - }); + try { + const response = await fetch(url, { + ...options, + signal: controller.signal, + headers: { + "Content-Type": "application/xml", + Authorization: createAuthHeader(), + ...options.headers, + }, + }); + // ... rest of the function + } finally { + clearTimeout(timeoutId); + }src/app/api/xmpp/accounts/[id]/route.ts (2)
199-208: Consider early return for already-deleted accounts.The handler allows deleting an account that's already in "deleted" status, which will redundantly call Prosody and update the database. Consider returning early if the account is already deleted.
Suggested improvement
if (!account) { return Response.json( { ok: false, error: "XMPP account not found" }, { status: 404 } ) } + + if (account.status === "deleted") { + return Response.json({ + ok: true, + message: "XMPP account already deleted", + }) + }
1-13: Missing Sentry integration for error capturing and tracing.Per coding guidelines, API routes should use
Sentry.captureException(error)in catch blocks andSentry.startSpan()to create spans for API calls with meaningfulnameandopproperties.Example integration
import type { NextRequest } from "next/server" import { and, eq, ne } from "drizzle-orm" +import * as Sentry from "@sentry/nextjs" import { APIError, handleAPIError, requireAuth } from "@/lib/api/utils"Then wrap each handler's logic:
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { return Sentry.startSpan( { name: 'GET /api/xmpp/accounts/[id]', op: 'http.server' }, async () => { try { // ... existing logic } catch (error) { Sentry.captureException(error) return handleAPIError(error) } } ) }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
.vscode/settings.jsonscripts/create-prosody-oauth-client.tssrc/app/(dashboard)/app/xmpp/page.tsxsrc/app/api/xmpp/accounts/[id]/route.tssrc/app/api/xmpp/accounts/route.tssrc/components/xmpp/xmpp-account-management.tsxsrc/hooks/use-xmpp-account.tssrc/lib/api/index.tssrc/lib/api/query-keys.tssrc/lib/api/xmpp.tssrc/lib/auth/config.tssrc/lib/db/schema/index.tssrc/lib/db/schema/xmpp.tssrc/lib/routes/config.tssrc/lib/xmpp/client.tssrc/lib/xmpp/config.tssrc/lib/xmpp/types.tssrc/lib/xmpp/utils.ts
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)
**/*.{js,jsx,ts,tsx}: UseSentry.captureException(error)to capture exceptions and log errors in Sentry, particularly in try-catch blocks or areas where exceptions are expected
UseSentry.startSpan()function to create spans for meaningful actions within applications like button clicks, API calls, and function calls
Files:
src/lib/xmpp/config.tssrc/lib/api/query-keys.tssrc/app/(dashboard)/app/xmpp/page.tsxsrc/lib/xmpp/client.tssrc/lib/api/xmpp.tssrc/app/api/xmpp/accounts/[id]/route.tssrc/lib/xmpp/utils.tssrc/lib/routes/config.tssrc/lib/db/schema/xmpp.tssrc/components/xmpp/xmpp-account-management.tsxsrc/app/api/xmpp/accounts/route.tsscripts/create-prosody-oauth-client.tssrc/hooks/use-xmpp-account.tssrc/lib/api/index.tssrc/lib/xmpp/types.tssrc/lib/db/schema/index.tssrc/lib/auth/config.ts
**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)
When creating custom spans for API calls with
Sentry.startSpan(), ensure thenameandopproperties are meaningful (e.g.,op: "http.client"with descriptive names likeGET /api/users/${userId}), and attach attributes based on relevant request information and metrics
Files:
src/lib/xmpp/config.tssrc/lib/api/query-keys.tssrc/lib/xmpp/client.tssrc/lib/api/xmpp.tssrc/app/api/xmpp/accounts/[id]/route.tssrc/lib/xmpp/utils.tssrc/lib/routes/config.tssrc/lib/db/schema/xmpp.tssrc/app/api/xmpp/accounts/route.tsscripts/create-prosody-oauth-client.tssrc/hooks/use-xmpp-account.tssrc/lib/api/index.tssrc/lib/xmpp/types.tssrc/lib/db/schema/index.tssrc/lib/auth/config.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)
**/*.{js,ts,jsx,tsx}: Import Sentry usingimport * as Sentry from "@sentry/nextjs"when using logs in NextJS projects
Reference the Sentry logger usingconst { logger } = Sentrywhen using logging functionality
Uselogger.fmtas a template literal function to bring variables into structured logs, providing better log formatting and variable interpolation
Files:
src/lib/xmpp/config.tssrc/lib/api/query-keys.tssrc/app/(dashboard)/app/xmpp/page.tsxsrc/lib/xmpp/client.tssrc/lib/api/xmpp.tssrc/app/api/xmpp/accounts/[id]/route.tssrc/lib/xmpp/utils.tssrc/lib/routes/config.tssrc/lib/db/schema/xmpp.tssrc/components/xmpp/xmpp-account-management.tsxsrc/app/api/xmpp/accounts/route.tsscripts/create-prosody-oauth-client.tssrc/hooks/use-xmpp-account.tssrc/lib/api/index.tssrc/lib/xmpp/types.tssrc/lib/db/schema/index.tssrc/lib/auth/config.ts
**/*.{css,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)
Use container query support with
@container,@sm:,@md:for container-based breakpoints and@max-md:for max-width queries
Files:
src/lib/xmpp/config.tssrc/lib/api/query-keys.tssrc/app/(dashboard)/app/xmpp/page.tsxsrc/lib/xmpp/client.tssrc/lib/api/xmpp.tssrc/app/api/xmpp/accounts/[id]/route.tssrc/lib/xmpp/utils.tssrc/lib/routes/config.tssrc/lib/db/schema/xmpp.tssrc/components/xmpp/xmpp-account-management.tsxsrc/app/api/xmpp/accounts/route.tsscripts/create-prosody-oauth-client.tssrc/hooks/use-xmpp-account.tssrc/lib/api/index.tssrc/lib/xmpp/types.tssrc/lib/db/schema/index.tssrc/lib/auth/config.ts
**/*.{ts,tsx,js,jsx,html}
📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)
**/*.{ts,tsx,js,jsx,html}: Use 3D transform utilities:transform-3d,rotate-x-*,rotate-y-*,rotate-z-*,scale-z-*,translate-z-*,perspective-*, andperspective-origin-*
Use linear gradient angles withbg-linear-45syntax and gradient interpolation likebg-linear-to-r/oklchorbg-linear-to-r/srgb
Use conic and radial gradients withbg-conicandbg-radial-[at_25%_25%]utilities
Useinset-shadow-*andinset-ring-*utilities instead of deprecated shadow opacity utilities
Usefield-sizing-contentutility for auto-resizing textareas
Usescheme-lightandscheme-darkutilities forcolor-schemeproperty
Usefont-stretch-*utilities for variable font configuration
Chain variants together for composable variants (e.g.,group-has-data-potato:opacity-100)
Usestartingvariant for@starting-styletransitions
Usenot-*variant for:not()pseudo-class (e.g.,not-first:mb-4)
Useinertvariant for styling elements with theinertattribute
Usenth-*variants:nth-3:,nth-last-5:,nth-of-type-4:,nth-last-of-type-6:for targeting specific elements
Usein-*variant as a simpler alternative togroup-*without addinggroupclass
Useopenvariant to support:popover-openpseudo-class
Use**variant for targeting all descendants
Replace deprecatedbg-opacity-*utilities with color values using slash notation (e.g.,bg-black/50)
Replace deprecatedtext-opacity-*utilities with color values using slash notation (e.g.,text-black/50)
Replace deprecatedborder-opacity-*,divide-opacity-*and similar opacity utilities with color slash notation
Useshadow-xsinstead ofshadow-smandshadow-sminstead ofshadow
Usedrop-shadow-xsinstead ofdrop-shadow-smanddrop-shadow-sminstead ofdrop-shadow
Useblur-xsinstead ofblur-smandblur-sminstead ofblur
Userounded-xsinstead ofrounded-smandrounded-sminstead ofrounded
Useoutline-hiddeninstead ofoutline-nonefor...
Files:
src/lib/xmpp/config.tssrc/lib/api/query-keys.tssrc/app/(dashboard)/app/xmpp/page.tsxsrc/lib/xmpp/client.tssrc/lib/api/xmpp.tssrc/app/api/xmpp/accounts/[id]/route.tssrc/lib/xmpp/utils.tssrc/lib/routes/config.tssrc/lib/db/schema/xmpp.tssrc/components/xmpp/xmpp-account-management.tsxsrc/app/api/xmpp/accounts/route.tsscripts/create-prosody-oauth-client.tssrc/hooks/use-xmpp-account.tssrc/lib/api/index.tssrc/lib/xmpp/types.tssrc/lib/db/schema/index.tssrc/lib/auth/config.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)
**/*.{ts,tsx}: Create QueryClient using getQueryClient() function that automatically handles server/client isolation (new instance per server request, singleton on client)
Always use the centralized query key factory from src/lib/api/query-keys.ts instead of creating keys manually
Include all variables used in queryFn in the query key to ensure proper cache management and query invalidation
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
**/*.{ts,tsx}: Use TypeScript strict mode
Use functional components and hooks in React
Prefer composition over inheritance
Use selective imports over barrel exports for performance
Write accessible, performant, type-safe code
Use explicit types for clarity, preferunknownoverany
Always await promises in async functions
Use semantic HTML and ARIA attributes for accessibility
Use@/authmodule for all authentication operations
Use@/dbfor all database operations
Use@/components/ui/*for importing shadcn/ui components with direct imports
Validate all inputs with Zod schemas
Implement proper RBAC with permissions module
Use TypeScript paths for imports configured in tsconfig.json
Implement proper error boundaries in React
Use Suspense for loading states
Optimize images with next/image
Use TanStack Query for all server state management
Files:
src/lib/xmpp/config.tssrc/lib/api/query-keys.tssrc/app/(dashboard)/app/xmpp/page.tsxsrc/lib/xmpp/client.tssrc/lib/api/xmpp.tssrc/app/api/xmpp/accounts/[id]/route.tssrc/lib/xmpp/utils.tssrc/lib/routes/config.tssrc/lib/db/schema/xmpp.tssrc/components/xmpp/xmpp-account-management.tsxsrc/app/api/xmpp/accounts/route.tsscripts/create-prosody-oauth-client.tssrc/hooks/use-xmpp-account.tssrc/lib/api/index.tssrc/lib/xmpp/types.tssrc/lib/db/schema/index.tssrc/lib/auth/config.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{ts,tsx,js,jsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Use function components over class components
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use thekeyprop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props in React components
Don't define components inside other components
Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, and use semantic elements instead of divs with roles
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Group related code together and separate concerns
Add `...
Files:
src/lib/xmpp/config.tssrc/lib/api/query-keys.tssrc/app/(dashboard)/app/xmpp/page.tsxsrc/lib/xmpp/client.tssrc/lib/api/xmpp.tssrc/app/api/xmpp/accounts/[id]/route.tssrc/lib/xmpp/utils.tssrc/lib/routes/config.tssrc/lib/db/schema/xmpp.tssrc/components/xmpp/xmpp-account-management.tsxsrc/app/api/xmpp/accounts/route.tsscripts/create-prosody-oauth-client.tssrc/hooks/use-xmpp-account.tssrc/lib/api/index.tssrc/lib/xmpp/types.tssrc/lib/db/schema/index.tssrc/lib/auth/config.ts
src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Handle internationalization via next-intl with locale files in
locale/directory
Files:
src/lib/xmpp/config.tssrc/lib/api/query-keys.tssrc/app/(dashboard)/app/xmpp/page.tsxsrc/lib/xmpp/client.tssrc/lib/api/xmpp.tssrc/app/api/xmpp/accounts/[id]/route.tssrc/lib/xmpp/utils.tssrc/lib/routes/config.tssrc/lib/db/schema/xmpp.tssrc/components/xmpp/xmpp-account-management.tsxsrc/app/api/xmpp/accounts/route.tssrc/hooks/use-xmpp-account.tssrc/lib/api/index.tssrc/lib/xmpp/types.tssrc/lib/db/schema/index.tssrc/lib/auth/config.ts
src/lib/api/query-keys.ts
📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)
src/lib/api/query-keys.ts: Query keys must maintain consistent array order (array order matters) and use serializable values only (no functions or non-serializable objects)
All query keys must follow the factory pattern with structure: resource.all, resource.lists(), resource.list(filters?), resource.detail(id), resource.details()
Files:
src/lib/api/query-keys.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)
When creating custom spans for component actions with
Sentry.startSpan(), ensure thenameandopproperties are meaningful for the activities in the call, and attach attributes based on relevant information and metrics
Files:
src/app/(dashboard)/app/xmpp/page.tsxsrc/components/xmpp/xmpp-account-management.tsx
{src/app/**,src/components/**}/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)
{src/app/**,src/components/**}/*.{ts,tsx}: Use useUsers(), useUser(), useSessions(), useAdminStats(), useUpdateUser(), useDeleteUser(), useDeleteSession() hooks from src/hooks/use-admin.ts for standard queries
Use corresponding Suspense hooks (useUsersSuspense(), useUserSuspense(), etc.) from src/hooks/use-admin-suspense.ts when wrapping components in Suspense and Error Boundaries
Files:
src/app/(dashboard)/app/xmpp/page.tsxsrc/app/api/xmpp/accounts/[id]/route.tssrc/components/xmpp/xmpp-account-management.tsxsrc/app/api/xmpp/accounts/route.ts
src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/app/**/*.{ts,tsx}: Follow Next.js App Router conventions
Never expose API keys in client code
Files:
src/app/(dashboard)/app/xmpp/page.tsxsrc/app/api/xmpp/accounts/[id]/route.tssrc/app/api/xmpp/accounts/route.ts
🧠 Learnings (21)
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to **/*.{ts,tsx} : Always use the centralized query key factory from src/lib/api/query-keys.ts instead of creating keys manually
Applied to files:
src/lib/api/query-keys.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/lib/api/query-keys.ts : All query keys must follow the factory pattern with structure: resource.all, resource.lists(), resource.list(filters?), resource.detail(id), resource.details()
Applied to files:
src/lib/api/query-keys.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to {src/app/**,src/components/**}/*.{ts,tsx} : Use useUsers(), useUser(), useSessions(), useAdminStats(), useUpdateUser(), useDeleteUser(), useDeleteSession() hooks from src/hooks/use-admin.ts for standard queries
Applied to files:
src/lib/api/query-keys.tssrc/hooks/use-xmpp-account.tssrc/lib/api/index.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/lib/api/query-keys.ts : Query keys must maintain consistent array order (array order matters) and use serializable values only (no functions or non-serializable objects)
Applied to files:
src/lib/api/query-keys.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to **/*.{ts,tsx} : Include all variables used in queryFn in the query key to ensure proper cache management and query invalidation
Applied to files:
src/lib/api/query-keys.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `next/head` or App Router metadata API for head elements
Applied to files:
src/app/(dashboard)/app/xmpp/page.tsx
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to src/app/**/*.{ts,tsx} : Follow Next.js App Router conventions
Applied to files:
src/app/(dashboard)/app/xmpp/page.tsx
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : Perform auth checks using Better Auth's auth.api.getSession() in Server Components before prefetching data, and redirect if not authenticated
Applied to files:
src/app/(dashboard)/app/xmpp/page.tsx
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : In Server Components, create a QueryClient per request using getServerQueryClient(), prefetch data in parallel with Promise.all(), and dehydrate using dehydrate(queryClient)
Applied to files:
src/app/(dashboard)/app/xmpp/page.tsxsrc/hooks/use-xmpp-account.tssrc/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : Use HydrationBoundary component from TanStack Query to transfer server-prefetched data to client cache in Server Components
Applied to files:
src/app/(dashboard)/app/xmpp/page.tsx
📚 Learning: 2025-12-18T18:18:05.202Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/anti-slop.mdc:0-0
Timestamp: 2025-12-18T18:18:05.202Z
Learning: Applies to **/*route*.{ts,tsx,js,jsx} : Clean, non-duplicated REST routes in backend code
Applied to files:
src/app/api/xmpp/accounts/[id]/route.tssrc/lib/routes/config.ts
📚 Learning: 2025-12-18T18:18:05.202Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/anti-slop.mdc:0-0
Timestamp: 2025-12-18T18:18:05.202Z
Learning: Applies to **/api/**/*.{ts,tsx,js,jsx} : API, DB, auth contracts, or tokens require mandatory review
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Use TanStack Query for all server state management
Applied to files:
src/hooks/use-xmpp-account.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Use functional components and hooks in React
Applied to files:
src/hooks/use-xmpp-account.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/hooks/use-admin.ts : Use queryClient.setQueryData() to update specific cache entries and queryClient.invalidateQueries() to invalidate related queries in mutation onSuccess handlers
Applied to files:
src/hooks/use-xmpp-account.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to **/*.{ts,tsx} : Create QueryClient using getQueryClient() function that automatically handles server/client isolation (new instance per server request, singleton on client)
Applied to files:
src/hooks/use-xmpp-account.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to {src/app/**,src/components/**}/*.{tsx} : Use useSuspenseQueries for parallel queries to avoid waterfalls and improve performance in Server Components and Client Components
Applied to files:
src/hooks/use-xmpp-account.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Avoid barrel files (index files that re-export everything)
Applied to files:
src/lib/api/index.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Use selective imports over barrel exports for performance
Applied to files:
src/lib/api/index.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Use `@/auth` module for all authentication operations
Applied to files:
src/lib/api/index.tssrc/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/lib/api/server-queries.ts : Server-side query functions in src/lib/api/server-queries.ts must use direct database access via ORM instead of HTTP requests
Applied to files:
src/lib/auth/config.ts
🧬 Code graph analysis (9)
src/app/(dashboard)/app/xmpp/page.tsx (3)
src/lib/api/hydration.ts (1)
getServerQueryClient(21-23)src/lib/api/query-keys.ts (1)
queryKeys(13-77)src/components/xmpp/xmpp-account-management.tsx (1)
XmppAccountManagement(37-292)
src/lib/xmpp/client.ts (3)
src/lib/xmpp/config.ts (1)
xmppConfig(9-32)src/lib/xmpp/types.ts (2)
ProsodyRestError(44-47)ProsodyRestAccountResponse(52-56)src/lib/xmpp/utils.ts (1)
formatJid(75-85)
src/lib/api/xmpp.ts (1)
src/lib/xmpp/types.ts (3)
XmppAccount(14-23)CreateXmppAccountRequest(28-30)UpdateXmppAccountRequest(35-39)
src/lib/xmpp/utils.ts (1)
src/lib/xmpp/types.ts (1)
XmppAccountStatus(9-9)
src/components/xmpp/xmpp-account-management.tsx (2)
src/hooks/use-xmpp-account.ts (3)
useXmppAccount(26-32)useCreateXmppAccount(49-61)useDeleteXmppAccount(94-106)src/lib/xmpp/types.ts (1)
XmppAccountStatus(9-9)
src/app/api/xmpp/accounts/route.ts (8)
src/app/api/xmpp/accounts/[id]/route.ts (2)
dynamic(13-13)GET(19-63)src/lib/api/utils.ts (3)
requireAuth(69-82)APIError(87-97)handleAPIError(103-125)src/lib/xmpp/types.ts (1)
CreateXmppAccountRequest(28-30)src/lib/db/schema/xmpp.ts (1)
xmppAccount(19-43)src/lib/xmpp/utils.ts (3)
isValidXmppUsername(28-41)generateUsernameFromEmail(49-67)formatJid(75-85)src/lib/xmpp/client.ts (3)
checkProsodyAccountExists(169-187)createProsodyAccount(72-114)deleteProsodyAccount(122-161)src/lib/xmpp/config.ts (1)
xmppConfig(9-32)src/lib/db/schema/auth.ts (1)
account(48-70)
scripts/create-prosody-oauth-client.ts (1)
src/lib/db/schema/oauth.ts (1)
oauthClient(5-36)
src/hooks/use-xmpp-account.ts (3)
src/lib/api/query-keys.ts (1)
queryKeys(13-77)src/lib/api/xmpp.ts (5)
fetchXmppAccount(25-42)fetchXmppAccountById(47-64)createXmppAccount(69-92)updateXmppAccount(97-121)deleteXmppAccount(126-139)src/lib/xmpp/types.ts (2)
CreateXmppAccountRequest(28-30)UpdateXmppAccountRequest(35-39)
src/lib/auth/config.ts (1)
src/lib/db/schema/xmpp.ts (1)
xmppAccount(19-43)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Sourcery review
- GitHub Check: Seer Code Review
🔇 Additional comments (35)
.vscode/settings.json (1)
42-44: LGTM, but appears unrelated to PR scope.The formatter change from
yzhang.markdown-all-in-onetoDavidAnson.vscode-markdownlintis valid. However, this settings change seems unrelated to the XMPP integration feature. Consider whether this should be in a separate commit or PR for cleaner history.scripts/create-prosody-oauth-client.ts (5)
1-7: LGTM!Imports are well-organized and use proper TypeScript path aliases. Using
node:cryptofor secure random generation is the correct choice.
9-26: LGTM!Clear and comprehensive documentation explaining the script's purpose, usage, and configuration options.
49-54: LGTM!Secure random generation using
randomByteswith base64url encoding. The entropy is sufficient for both client ID and secret.
88-99: LGTM with optional enhancement.Error handling is appropriate for a CLI script. The pattern of logging details and rethrowing is correct. If Sentry is initialized in CLI contexts, you could optionally add
Sentry.captureException(error)per coding guidelines, but this may not be necessary for a setup script.
102-110: Therequire.main === modulepattern is compatible with this project's setup. The project usestsxfor running TypeScript scripts (as seen indb:seedandcreate-adminscripts), which transpiles to CommonJS at runtime. Since there is no"type": "module"in package.json, this pattern works correctly.However, the script should use proper error handling with Sentry instead of
console.errorfor production logging, per the project's guidelines.Likely an incorrect or invalid review comment.
src/lib/xmpp/types.ts (1)
1-56: LGTM! Well-structured type definitions.The types are clearly documented and follow TypeScript best practices. The use of
Record<string, unknown>for metadata and explicit status types provides good type safety.src/lib/xmpp/utils.ts (3)
92-108: Consider: JIDs with resource parts will fail parsing.XMPP JIDs can include a resource component (
user@domain/resource). The current implementation rejects these as invalid. If this is intentional for bare JID handling, consider documenting this limitation or adding a note in the JSDoc.
28-41: LGTM!The validation logic correctly checks for null/undefined, length constraints, and regex pattern matching.
115-121: LGTM!The type guard correctly narrows the string type to
XmppAccountStatus. The use ofas constensures proper inference.src/lib/api/index.ts (1)
14-14: LGTM!The new export follows the established pattern in this barrel file. The existing
biome-ignoredirective already acknowledges this pattern.src/lib/routes/config.ts (2)
150-161: Verify: Should the XMPP route have permission restrictions?The admin route at line 172 has
permissions: ["canViewAdmin"]for role-based access, but the new xmpp route has no permissions defined. This means any authenticated user can access it.If XMPP account management should be restricted to certain roles, consider adding a permissions array similar to the admin route.
6-6: LGTM!The
MessageSquareicon import follows the existing pattern and is appropriate for an XMPP/messaging feature.src/lib/db/schema/index.ts (2)
16-16: LGTM!The import follows the established pattern for schema module organization.
30-30: LGTM!The
xmppAccounttable is properly added to the exported schema object, making it available for database operations throughout the application.src/lib/api/query-keys.ts (1)
69-76: LGTM!The new
xmppAccountsquery key factory follows the established pattern and learnings for type-safe query key management. The simpler structure (withoutlists/listmethods) is appropriate given the 1:1 relationship between users and XMPP accounts.src/lib/db/schema/xmpp.ts (1)
19-36: LGTM!The table schema is well-designed:
- Cascade delete on
userIdensures proper cleanup when users are deleted- Unique constraint on
userIdenforces the 1:1 relationship- The
$onUpdatepattern forupdatedAtis correct for drizzle-ormsrc/lib/auth/config.ts (1)
22-26: LGTM!The imports are correctly structured and follow the project's conventions for importing from
@/lib/db/schema/*.src/hooks/use-xmpp-account.ts (2)
26-32: LGTM!The
useXmppAccounthook follows TanStack Query best practices with proper use of the centralized query key factory and an appropriate stale time for the data characteristics. Based on learnings, this correctly usesqueryKeys.xmppAccounts.current().
37-44: LGTM!The
useXmppAccountByIdhook correctly uses theenabledoption to prevent unnecessary queries whenidis falsy, and includes theidin the query key for proper cache isolation per TanStack Query guidelines.src/lib/xmpp/config.ts (1)
9-39: LGTM on config structure and password validation.The configuration object is well-structured with proper
as constassertion for type safety. The password validation correctly ensures sensitive credentials are provided via environment variables rather than defaults.src/components/xmpp/xmpp-account-management.tsx (3)
99-120: LGTM!Loading and error states are well implemented with appropriate visual feedback using icons and consistent Card layouts.
122-168: LGTM!The create form properly handles the pending state, disables input during submission, and provides clear user guidance. The empty string to
undefinedconversion for optional username is correctly handled.
253-288: LGTM!The delete confirmation flow using AlertDialog is well implemented with clear warning messaging and proper destructive button styling.
src/app/(dashboard)/app/xmpp/page.tsx (2)
12-16: LGTM!Metadata generation follows the established pattern using route config and resolver.
23-25: LGTM!Session verification is correctly performed before rendering, per learnings from coding guidelines.
src/app/api/xmpp/accounts/route.ts (2)
28-43: LGTM on auth and existing account check.The authentication check and one-account-per-user constraint are properly implemented.
176-206: LGTM!The GET handler is well-implemented with proper authentication and clean response structure.
src/lib/xmpp/client.ts (3)
1-5: LGTM!Good use of
"server-only"directive to ensure this module is only imported in server contexts, protecting credentials from client bundles.
122-161: LGTM!The
deleteProsodyAccountfunction correctly handles the idempotent case where the account doesn't exist (404), returning success instead of throwing.
169-187: LGTM!The
checkProsodyAccountExistsfunction properly distinguishes between "not found" (return false) and other errors (re-throw), which is the correct pattern for existence checks.src/lib/api/xmpp.ts (3)
25-42: LGTM!The
fetchXmppAccountfunction correctly handles the 404 case by returningnullinstead of throwing, which is appropriate for "account not found" semantics.
69-92: LGTM!The
createXmppAccountfunction has proper error handling and validates that an account is returned in the response.
126-139: LGTM!The
deleteXmppAccountfunction is clean and follows the established error handling pattern.src/app/api/xmpp/accounts/[id]/route.ts (1)
19-63: LGTM! Consider adding Sentry span for observability.The GET handler correctly implements authentication, authorization (owner or admin), and error handling. Per coding guidelines, consider wrapping the handler logic in
Sentry.startSpan()withop: "http.server"for tracing.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
src/app/api/xmpp/accounts/route.ts
Outdated
| // Check username uniqueness in database | ||
| const existingUsername = await db.query.xmppAccount.findFirst({ | ||
| where: eq(xmppAccount.username, username), | ||
| }); | ||
|
|
||
| if (existingUsername) { | ||
| return Response.json( | ||
| { ok: false, error: "Username already taken" }, | ||
| { status: 409 } | ||
| ); | ||
| } | ||
|
|
||
| // Check username uniqueness in Prosody | ||
| const prosodyAccountExists = await checkProsodyAccountExists(username); | ||
| if (prosodyAccountExists) { | ||
| return Response.json( | ||
| { ok: false, error: "Username already taken in XMPP server" }, | ||
| { status: 409 } | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TOCTOU race condition in uniqueness checks.
There's a time-of-check-to-time-of-use gap between checking username uniqueness (lines 91-109) and creating the account (lines 112-129). Two concurrent requests with the same username could both pass the checks before either creates the account.
The rollback at line 147 partially mitigates this, but consider:
- Using a database transaction with unique constraint as the primary guard
- Relying on Prosody's conflict detection as the authoritative check
The current error handling at line 117 (error.message.includes("already exists")) does handle this case, but the user experience could be improved by having a clearer error path.
🤖 Prompt for AI Agents
In `@src/app/api/xmpp/accounts/route.ts` around lines 90 - 109, The uniqueness
checks using db.query.xmppAccount.findFirst and checkProsodyAccountExists have a
TOCTOU race: two concurrent requests can pass both checks before creation; fix
by performing the account creation inside a database transaction (use the same
transaction for any DB inserts/queries) relying on a UNIQUE constraint on
xmppAccount.username as the primary guard, and handle the constraint violation
error from the transactional insert to return a 409 response; also treat Prosody
creation (createProsodyAccount) as authoritative where possible—attempt Prosody
create first or roll back DB insert on Prosody conflict—and consolidate error
handling currently at the rollback path to detect DB unique-violation and
Prosody "already exists" errors and return a consistent "Username already taken"
409 response.
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @kzndotsh. * #3 (comment) The following files were modified: * `scripts/create-prosody-oauth-client.ts` * `src/app/(dashboard)/app/xmpp/page.tsx` * `src/app/api/xmpp/accounts/[id]/route.ts` * `src/app/api/xmpp/accounts/route.ts` * `src/components/xmpp/xmpp-account-management.tsx` * `src/hooks/use-xmpp-account.ts` * `src/lib/api/xmpp.ts` * `src/lib/xmpp/client.ts` * `src/lib/xmpp/utils.ts`
- Updated the XMPP account creation and update API routes to include Zod validation for request bodies, ensuring better error handling and data integrity. - Improved username handling by implementing a dedicated function to determine username availability and format. - Enhanced error handling for Prosody account operations, introducing a custom error class for better clarity. - Updated the clipboard copy functionality in the XMPP account management component to support older browsers. - Refactored code for improved readability and maintainability across various files related to XMPP account management.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@scripts/create-prosody-oauth-client.ts`:
- Line 68: The diff enables the deprecated "password" grant in grantTypes for
create-prosody-oauth-client; ensure compensating controls by (1) enforcing rate
limiting on the token endpoint that issues tokens for the password grant
(implement or configure per-client and per-IP throttling and brute-force
protections where tokens are minted), (2) verifying PROSODY_OAUTH_CLIENT_ID and
PROSODY_OAUTH_CLIENT_SECRET are loaded only from secure runtime secrets
(environment variables or secret manager) and not checked into VCS, and (3) add
a short-term migration plan note in the code/comments to move clients from the
password grant to authorization_code + PKCE once Prosody XMPP support allows
(update grantTypes usage where grantTypes: ["authorization_code","password"] is
set and document steps to rotate secrets and adopt PKCE).
In `@src/lib/xmpp/client.ts`:
- Around line 66-75: The error handler inside the Prosody REST response check
currently assumes JSON; update the block that throws on !response.ok (the code
that builds errorMessage) to inspect response.headers.get('content-type') and
branch: if JSON (includes 'application/json' or '+json') parse response.json()
and use errorData.error || errorData.message; if XML (includes 'application/xml'
or 'text/xml' or '+xml') parse response.text(), run a minimal XML extraction to
pull a <message> or <error> node or otherwise use the raw text; fallback to
response.statusText if parsing fails; ensure all parsing is wrapped in try/catch
so the thrown Error contains the most specific errorMessage possible.
♻️ Duplicate comments (1)
src/app/api/xmpp/accounts/route.ts (1)
71-104: Race window between availability checks and creation.
Concurrent requests can still pass the checks and create duplicates unless a DB-level unique constraint + transactional insert is the primary guard. Consider consolidating checks with the insert (and mapping unique-violation / Prosody conflict to 409).Also applies to: 169-200
🧹 Nitpick comments (8)
src/components/xmpp/xmpp-account-management.tsx (3)
43-60: Consider adding Sentry error capture for failed mutations.Per coding guidelines, exceptions should be captured with
Sentry.captureException(error)in catch blocks. ThehandleCreatefunction catches errors but only displays a toast without reporting to Sentry for monitoring.♻️ Suggested improvement
+import * as Sentry from '@sentry/nextjs' const handleCreate = async () => { try { await createMutation.mutateAsync({ username: username.trim() || undefined, }); toast.success("XMPP account created", { description: "Your XMPP account has been created successfully.", }); setUsername(""); } catch (error) { + Sentry.captureException(error, { + tags: { action: 'xmpp_account_create' }, + }); toast.error("Failed to create account", { description: error instanceof Error ? error.message : "An error occurred while creating your XMPP account.", }); } };
62-80: Add Sentry error capture for delete failures.Similar to
handleCreate, thehandleDeletefunction should report errors to Sentry for observability per coding guidelines.♻️ Suggested improvement
const handleDelete = async () => { if (!account) { return; } try { await deleteMutation.mutateAsync(account.id); toast.success("XMPP account deleted", { description: "Your XMPP account has been deleted successfully.", }); } catch (error) { + Sentry.captureException(error, { + tags: { action: 'xmpp_account_delete' }, + }); toast.error("Failed to delete account", { description: error instanceof Error ? error.message : "An error occurred while deleting your XMPP account.", }); } };
249-252: Consider using locale-aware date formatting.
toLocaleDateString()without options may produce inconsistent results across locales. Since internationalization is handled via next-intl per coding guidelines, consider using a formatter from that library or explicitly specifying locale options.♻️ Example with explicit options
- {new Date(account.createdAt).toLocaleDateString()} + {new Date(account.createdAt).toLocaleDateString(undefined, { + year: 'numeric', + month: 'long', + day: 'numeric', + })}scripts/create-prosody-oauth-client.ts (1)
106-112: Consider using ES module syntax for consistency.The script uses
require.main === modulewhich is CommonJS style. If the project uses ES modules, consider usingimport.meta.urlbased detection or a separate entry point.src/lib/xmpp/utils.ts (1)
92-108: Consider handling JIDs with resource parts.The
parseJidfunction assumes JIDs are inuser@domainformat, but XMPP JIDs can include a resource:user@domain/resource. If such JIDs are passed, this function will incorrectly reject them or misparse them.♻️ Suggested fix to handle resource parts
export function parseJid(jid: string): { username: string; domain: string } { if (!jid || typeof jid !== "string") { throw new Error("Invalid JID"); } - const parts = jid.split("@"); - if (parts.length !== 2) { + // Handle optional resource part: user@domain/resource + const atIndex = jid.indexOf("@"); + if (atIndex === -1) { throw new Error(`Invalid JID format: ${jid}`); } - const [username, domain] = parts; - if (!(username && domain)) { + const username = jid.slice(0, atIndex); + let domain = jid.slice(atIndex + 1); + + // Strip resource part if present + const slashIndex = domain.indexOf("/"); + if (slashIndex !== -1) { + domain = domain.slice(0, slashIndex); + } + + if (!username || !domain) { throw new Error(`Invalid JID format: ${jid}`); } return { username, domain }; }src/lib/xmpp/client.ts (3)
106-111: Consider escaping the domain for consistency.While the username is now properly escaped,
xmppConfig.domainis also interpolated into the XML stanza. For defense-in-depth consistency, consider escaping it as well, even though it comes from configuration.♻️ Suggested improvement
const stanza = `<?xml version="1.0"?> -<iq type="set" to="${xmppConfig.domain}" id="create_${Date.now()}"> +<iq type="set" to="${escapeXml(xmppConfig.domain)}" id="create_${Date.now()}"> <query xmlns="jabber:iq:register"> <username>${escapeXml(username)}</username> </query> </iq>`;
57-64: Add request timeout to prevent indefinite hangs.The
fetchcall has no timeout configured. If the Prosody server becomes unresponsive, this could block indefinitely. Consider usingAbortControllerwith a timeout.♻️ Suggested improvement with timeout
+const REQUEST_TIMEOUT_MS = 30000; // 30 seconds async function prosodyRequest<T>( endpoint: string, options: RequestInit = {} ): Promise<T> { const { restUrl } = xmppConfig.prosody; const url = `${restUrl}${endpoint}`; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); + - const response = await fetch(url, { - ...options, - headers: { - "Content-Type": "application/xml", - Authorization: createAuthHeader(), - ...options.headers, - }, - }); + try { + const response = await fetch(url, { + ...options, + signal: controller.signal, + headers: { + "Content-Type": "application/xml", + Authorization: createAuthHeader(), + ...options.headers, + }, + }); + // ... rest of the function + } finally { + clearTimeout(timeoutId); + }
123-136: Consider adding Sentry error capture for Prosody operations.Per coding guidelines, exceptions should be captured with
Sentry.captureException(error). The Prosody API client functions handle errors but don't report them to Sentry, which would help with monitoring and debugging production issues.♻️ Example for createProsodyAccount
+import * as Sentry from '@sentry/nextjs' } catch (error) { + Sentry.captureException(error, { + tags: { operation: 'create_prosody_account' }, + extra: { username }, + }); if (error instanceof Error) { // Check if account already exists if ( error.message.includes("exists") ||
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
.cursor/hooks.jsonscripts/create-prosody-oauth-client.tssrc/app/(dashboard)/app/xmpp/page.tsxsrc/app/api/xmpp/accounts/[id]/route.tssrc/app/api/xmpp/accounts/route.tssrc/components/xmpp/xmpp-account-management.tsxsrc/lib/auth/config.tssrc/lib/xmpp/client.tssrc/lib/xmpp/config.tssrc/lib/xmpp/utils.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/lib/xmpp/config.ts
- src/app/(dashboard)/app/xmpp/page.tsx
- src/app/api/xmpp/accounts/[id]/route.ts
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)
**/*.{js,jsx,ts,tsx}: UseSentry.captureException(error)to capture exceptions and log errors in Sentry, particularly in try-catch blocks or areas where exceptions are expected
UseSentry.startSpan()function to create spans for meaningful actions within applications like button clicks, API calls, and function calls
Files:
src/app/api/xmpp/accounts/route.tssrc/lib/auth/config.tssrc/components/xmpp/xmpp-account-management.tsxscripts/create-prosody-oauth-client.tssrc/lib/xmpp/utils.tssrc/lib/xmpp/client.ts
**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)
When creating custom spans for API calls with
Sentry.startSpan(), ensure thenameandopproperties are meaningful (e.g.,op: "http.client"with descriptive names likeGET /api/users/${userId}), and attach attributes based on relevant request information and metrics
Files:
src/app/api/xmpp/accounts/route.tssrc/lib/auth/config.tsscripts/create-prosody-oauth-client.tssrc/lib/xmpp/utils.tssrc/lib/xmpp/client.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)
**/*.{js,ts,jsx,tsx}: Import Sentry usingimport * as Sentry from "@sentry/nextjs"when using logs in NextJS projects
Reference the Sentry logger usingconst { logger } = Sentrywhen using logging functionality
Uselogger.fmtas a template literal function to bring variables into structured logs, providing better log formatting and variable interpolation
Files:
src/app/api/xmpp/accounts/route.tssrc/lib/auth/config.tssrc/components/xmpp/xmpp-account-management.tsxscripts/create-prosody-oauth-client.tssrc/lib/xmpp/utils.tssrc/lib/xmpp/client.ts
**/*.{css,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)
Use container query support with
@container,@sm:,@md:for container-based breakpoints and@max-md:for max-width queries
Files:
src/app/api/xmpp/accounts/route.tssrc/lib/auth/config.tssrc/components/xmpp/xmpp-account-management.tsxscripts/create-prosody-oauth-client.tssrc/lib/xmpp/utils.tssrc/lib/xmpp/client.ts
**/*.{ts,tsx,js,jsx,html}
📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)
**/*.{ts,tsx,js,jsx,html}: Use 3D transform utilities:transform-3d,rotate-x-*,rotate-y-*,rotate-z-*,scale-z-*,translate-z-*,perspective-*, andperspective-origin-*
Use linear gradient angles withbg-linear-45syntax and gradient interpolation likebg-linear-to-r/oklchorbg-linear-to-r/srgb
Use conic and radial gradients withbg-conicandbg-radial-[at_25%_25%]utilities
Useinset-shadow-*andinset-ring-*utilities instead of deprecated shadow opacity utilities
Usefield-sizing-contentutility for auto-resizing textareas
Usescheme-lightandscheme-darkutilities forcolor-schemeproperty
Usefont-stretch-*utilities for variable font configuration
Chain variants together for composable variants (e.g.,group-has-data-potato:opacity-100)
Usestartingvariant for@starting-styletransitions
Usenot-*variant for:not()pseudo-class (e.g.,not-first:mb-4)
Useinertvariant for styling elements with theinertattribute
Usenth-*variants:nth-3:,nth-last-5:,nth-of-type-4:,nth-last-of-type-6:for targeting specific elements
Usein-*variant as a simpler alternative togroup-*without addinggroupclass
Useopenvariant to support:popover-openpseudo-class
Use**variant for targeting all descendants
Replace deprecatedbg-opacity-*utilities with color values using slash notation (e.g.,bg-black/50)
Replace deprecatedtext-opacity-*utilities with color values using slash notation (e.g.,text-black/50)
Replace deprecatedborder-opacity-*,divide-opacity-*and similar opacity utilities with color slash notation
Useshadow-xsinstead ofshadow-smandshadow-sminstead ofshadow
Usedrop-shadow-xsinstead ofdrop-shadow-smanddrop-shadow-sminstead ofdrop-shadow
Useblur-xsinstead ofblur-smandblur-sminstead ofblur
Userounded-xsinstead ofrounded-smandrounded-sminstead ofrounded
Useoutline-hiddeninstead ofoutline-nonefor...
Files:
src/app/api/xmpp/accounts/route.tssrc/lib/auth/config.tssrc/components/xmpp/xmpp-account-management.tsxscripts/create-prosody-oauth-client.tssrc/lib/xmpp/utils.tssrc/lib/xmpp/client.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)
**/*.{ts,tsx}: Create QueryClient using getQueryClient() function that automatically handles server/client isolation (new instance per server request, singleton on client)
Always use the centralized query key factory from src/lib/api/query-keys.ts instead of creating keys manually
Include all variables used in queryFn in the query key to ensure proper cache management and query invalidation
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
**/*.{ts,tsx}: Use TypeScript strict mode
Use functional components and hooks in React
Prefer composition over inheritance
Use selective imports over barrel exports for performance
Write accessible, performant, type-safe code
Use explicit types for clarity, preferunknownoverany
Always await promises in async functions
Use semantic HTML and ARIA attributes for accessibility
Use@/authmodule for all authentication operations
Use@/dbfor all database operations
Use@/components/ui/*for importing shadcn/ui components with direct imports
Validate all inputs with Zod schemas
Implement proper RBAC with permissions module
Use TypeScript paths for imports configured in tsconfig.json
Implement proper error boundaries in React
Use Suspense for loading states
Optimize images with next/image
Use TanStack Query for all server state management
Files:
src/app/api/xmpp/accounts/route.tssrc/lib/auth/config.tssrc/components/xmpp/xmpp-account-management.tsxscripts/create-prosody-oauth-client.tssrc/lib/xmpp/utils.tssrc/lib/xmpp/client.ts
{src/app/**,src/components/**}/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)
{src/app/**,src/components/**}/*.{ts,tsx}: Use useUsers(), useUser(), useSessions(), useAdminStats(), useUpdateUser(), useDeleteUser(), useDeleteSession() hooks from src/hooks/use-admin.ts for standard queries
Use corresponding Suspense hooks (useUsersSuspense(), useUserSuspense(), etc.) from src/hooks/use-admin-suspense.ts when wrapping components in Suspense and Error Boundaries
Files:
src/app/api/xmpp/accounts/route.tssrc/components/xmpp/xmpp-account-management.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{ts,tsx,js,jsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Use function components over class components
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use thekeyprop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props in React components
Don't define components inside other components
Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, and use semantic elements instead of divs with roles
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Group related code together and separate concerns
Add `...
Files:
src/app/api/xmpp/accounts/route.tssrc/lib/auth/config.tssrc/components/xmpp/xmpp-account-management.tsxscripts/create-prosody-oauth-client.tssrc/lib/xmpp/utils.tssrc/lib/xmpp/client.ts
src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/app/**/*.{ts,tsx}: Follow Next.js App Router conventions
Never expose API keys in client code
Files:
src/app/api/xmpp/accounts/route.ts
src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Handle internationalization via next-intl with locale files in
locale/directory
Files:
src/app/api/xmpp/accounts/route.tssrc/lib/auth/config.tssrc/components/xmpp/xmpp-account-management.tsxsrc/lib/xmpp/utils.tssrc/lib/xmpp/client.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)
When creating custom spans for component actions with
Sentry.startSpan(), ensure thenameandopproperties are meaningful for the activities in the call, and attach attributes based on relevant information and metrics
Files:
src/components/xmpp/xmpp-account-management.tsx
🧠 Learnings (8)
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Use `@/auth` module for all authentication operations
Applied to files:
src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:15:18.973Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/sentry.mdc:0-0
Timestamp: 2026-01-15T06:15:18.973Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use `Sentry.captureException(error)` to capture exceptions and log errors in Sentry, particularly in try-catch blocks or areas where exceptions are expected
Applied to files:
src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Handle errors appropriately in async code with try-catch blocks
Applied to files:
src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/lib/api/server-queries.ts : Server-side query functions in src/lib/api/server-queries.ts must use direct database access via ORM instead of HTTP requests
Applied to files:
src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : In Server Components, create a QueryClient per request using getServerQueryClient(), prefetch data in parallel with Promise.all(), and dehydrate using dehydrate(queryClient)
Applied to files:
src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to src/app/**/*.{ts,tsx} : Never expose API keys in client code
Applied to files:
scripts/create-prosody-oauth-client.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Specify all dependencies in hook dependency arrays correctly
Applied to files:
.cursor/hooks.json
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Validate and sanitize user input
Applied to files:
src/lib/xmpp/utils.ts
🧬 Code graph analysis (5)
src/lib/auth/config.ts (1)
src/lib/db/schema/xmpp.ts (1)
xmppAccount(19-43)
src/components/xmpp/xmpp-account-management.tsx (2)
src/hooks/use-xmpp-account.ts (3)
useXmppAccount(26-32)useCreateXmppAccount(49-61)useDeleteXmppAccount(94-106)src/lib/xmpp/types.ts (1)
XmppAccountStatus(9-9)
scripts/create-prosody-oauth-client.ts (1)
src/lib/db/schema/oauth.ts (1)
oauthClient(5-36)
src/lib/xmpp/utils.ts (1)
src/lib/xmpp/types.ts (1)
XmppAccountStatus(9-9)
src/lib/xmpp/client.ts (3)
src/lib/xmpp/config.ts (1)
xmppConfig(9-32)src/lib/xmpp/types.ts (2)
ProsodyRestError(44-47)ProsodyRestAccountResponse(52-56)src/lib/xmpp/utils.ts (1)
formatJid(75-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Seer Code Review
- GitHub Check: Sourcery review
🔇 Additional comments (12)
.cursor/hooks.json (1)
6-6: LGTM!Adding
type-checkto theafterFileEdithook is a sensible enhancement given the TypeScript-heavy additions in this PR. The sequential&&execution ensures type checking only runs when the initial checks pass, avoiding redundant work on failing builds.src/components/xmpp/xmpp-account-management.tsx (1)
191-216: LGTM! Clipboard fallback is well-implemented.The clipboard copy functionality now properly handles both modern and legacy browsers with appropriate error handling and user feedback. The past review concern about
navigator.clipboardfailures has been addressed with a try-catch wrapper and a fallback usingdocument.execCommand.src/lib/auth/config.ts (2)
281-316: LGTM! Error handling properly implemented.The
customUserInfoClaimsfunction now includes proper try-catch with Sentry error capture, addressing the previous review concern. The graceful degradation (continuing without XMPP claim on error) is appropriate for OAuth flows.
252-252: LGTM! XMPP scope added correctly.The
"xmpp"scope is properly added to the supported scopes list, enabling XMPP-related claims in OAuth flows.scripts/create-prosody-oauth-client.ts (1)
39-50: LGTM! Security concern addressed.The script now properly masks the client secret for existing clients with
"(set - not displayed for security)", addressing the previous review concern about exposing secrets in logs.src/lib/xmpp/utils.ts (2)
17-21: LGTM! Leading character sanitization fixed.The
XMPP_USERNAME_START_REGEXnow correctly uses/^[^a-zA-Z0-9]+/with the+quantifier to remove ALL leading non-alphanumeric characters in one replacement, addressing the previous review concern.
28-41: LGTM! Username validation is thorough.The
isValidXmppUsernamefunction properly validates input type, length bounds, and format using the regex. The defensive checks for falsy values and type are good practice.src/lib/xmpp/client.ts (1)
21-28: LGTM! XML escaping implemented.The
escapeXmlhelper function properly escapes all XML special characters (&,<,>,",'), addressing the previous review concern about potential XML injection vulnerabilities.src/app/api/xmpp/accounts/route.ts (4)
1-27: Solid schema + dynamic route setup.
Clear and minimal request validation scope for the create endpoint.
32-66: Username derivation/validation flow looks good.
Clean error handling and a predictable fallback path.
238-262: GET handler response mapping is clean and complete.
Includes all relevant fields with a clear 404 path.
225-226:handleAPIErroralready captures exceptions to Sentry—no additional capture needed.The
handleAPIErrorfunction already callscaptureError()with proper context tags, which internally callsSentry.captureException()from@sentry/nextjs. Adding explicitSentry.captureException(error)calls beforehandleAPIErrorin the catch blocks would be redundant and violate the guideline against catching errors just to rethrow them.Likely an incorrect or invalid review comment.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| clientSecret, | ||
| name: clientName, | ||
| redirectUris: [], // Not needed for server-to-server auth | ||
| grantTypes: ["authorization_code", "password"], // Password grant for legacy clients |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
OAuth 2.1 password grant type deprecation status
💡 Result:
Short answer: The Resource Owner Password Credentials (password/ROPC) grant is deprecated and omitted from OAuth 2.1; the OAuth community’s Security Best Current Practice also disallows its use. Use Authorization Code + PKCE for user sign-in (or Client Credentials for machine-to-machine). (As of Jan 16, 2026.) Sources: [IETF OAuth 2.1 draft][1], oauth.net guidance on the password grant and OAuth 2.1 [2][3].
Sources
- IETF draft-ietf-oauth-v2-1 (OAuth 2.1) — ROPC omitted. [turn0search0]
- OAuth.net — “OAuth 2.0 Password Grant” (disallowed by Security BCP). [turn0search2]
- OAuth.net — OAuth 2.1 overview (password grant omitted; use auth code + PKCE). [turn0search1]
🏁 Script executed:
cd /repo && find . -name "create-prosody-oauth-client.ts" -type fRepository: allthingslinux/portal
Length of output: 120
🏁 Script executed:
git ls-files | grep -E "(create-prosody|oauth)" | head -20Repository: allthingslinux/portal
Length of output: 358
🏁 Script executed:
cat -n scripts/create-prosody-oauth-client.tsRepository: allthingslinux/portal
Length of output: 4827
Verify rate limiting and secure storage are implemented for the password grant flow.
The password grant type is deprecated in OAuth 2.1 and disallowed by Security BCP. While this is documented as intentional for legacy XMPP client support, ensure compensating controls are in place:
- Rate limiting is enabled on the token endpoint to prevent credential abuse
- Client credentials (PROSODY_OAUTH_CLIENT_ID and PROSODY_OAUTH_CLIENT_SECRET) are stored securely in environment variables, not committed to version control
- Plan migration path to
authorization_code+ PKCE flow once Prosody XMPP client support allows
🤖 Prompt for AI Agents
In `@scripts/create-prosody-oauth-client.ts` at line 68, The diff enables the
deprecated "password" grant in grantTypes for create-prosody-oauth-client;
ensure compensating controls by (1) enforcing rate limiting on the token
endpoint that issues tokens for the password grant (implement or configure
per-client and per-IP throttling and brute-force protections where tokens are
minted), (2) verifying PROSODY_OAUTH_CLIENT_ID and PROSODY_OAUTH_CLIENT_SECRET
are loaded only from secure runtime secrets (environment variables or secret
manager) and not checked into VCS, and (3) add a short-term migration plan note
in the code/comments to move clients from the password grant to
authorization_code + PKCE once Prosody XMPP support allows (update grantTypes
usage where grantTypes: ["authorization_code","password"] is set and document
steps to rotate secrets and adopt PKCE).
| if (!response.ok) { | ||
| let errorMessage = `Prosody REST API error: ${response.status} ${response.statusText}`; | ||
| try { | ||
| const errorData = (await response.json()) as ProsodyRestError; | ||
| errorMessage = errorData.error || errorData.message || errorMessage; | ||
| } catch { | ||
| // If response is not JSON, use status text | ||
| } | ||
| throw new Error(errorMessage); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Content-Type mismatch in error handling.
The request sends Content-Type: application/xml but the error handler attempts to parse the response as JSON (line 69). If Prosody returns XML error responses, this will silently fail and use the generic status text.
🔧 Suggested fix to handle both XML and JSON errors
if (!response.ok) {
let errorMessage = `Prosody REST API error: ${response.status} ${response.statusText}`;
+ const contentType = response.headers.get("content-type") || "";
try {
- const errorData = (await response.json()) as ProsodyRestError;
- errorMessage = errorData.error || errorData.message || errorMessage;
+ if (contentType.includes("application/json")) {
+ const errorData = (await response.json()) as ProsodyRestError;
+ errorMessage = errorData.error || errorData.message || errorMessage;
+ } else {
+ const text = await response.text();
+ if (text) {
+ errorMessage = `${errorMessage}: ${text.slice(0, 200)}`;
+ }
+ }
} catch {
// If response is not JSON, use status text
}
throw new Error(errorMessage);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!response.ok) { | |
| let errorMessage = `Prosody REST API error: ${response.status} ${response.statusText}`; | |
| try { | |
| const errorData = (await response.json()) as ProsodyRestError; | |
| errorMessage = errorData.error || errorData.message || errorMessage; | |
| } catch { | |
| // If response is not JSON, use status text | |
| } | |
| throw new Error(errorMessage); | |
| } | |
| if (!response.ok) { | |
| let errorMessage = `Prosody REST API error: ${response.status} ${response.statusText}`; | |
| const contentType = response.headers.get("content-type") || ""; | |
| try { | |
| if (contentType.includes("application/json")) { | |
| const errorData = (await response.json()) as ProsodyRestError; | |
| errorMessage = errorData.error || errorData.message || errorMessage; | |
| } else { | |
| const text = await response.text(); | |
| if (text) { | |
| errorMessage = `${errorMessage}: ${text.slice(0, 200)}`; | |
| } | |
| } | |
| } catch { | |
| // If response is not JSON, use status text | |
| } | |
| throw new Error(errorMessage); | |
| } |
🤖 Prompt for AI Agents
In `@src/lib/xmpp/client.ts` around lines 66 - 75, The error handler inside the
Prosody REST response check currently assumes JSON; update the block that throws
on !response.ok (the code that builds errorMessage) to inspect
response.headers.get('content-type') and branch: if JSON (includes
'application/json' or '+json') parse response.json() and use errorData.error ||
errorData.message; if XML (includes 'application/xml' or 'text/xml' or '+xml')
parse response.text(), run a minimal XML extraction to pull a <message> or
<error> node or otherwise use the raw text; fallback to response.statusText if
parsing fails; ensure all parsing is wrapped in try/catch so the thrown Error
contains the most specific errorMessage possible.
- Changed the parameter extraction method in the GET, PATCH, and DELETE API routes for XMPP accounts to use RouteContext for improved type safety and consistency. - This refactor enhances code readability and aligns with the updated routing structure.
| if (!newAccount) { | ||
| // Rollback: Try to delete Prosody account if database insert fails | ||
| try { | ||
| await deleteProsodyAccount(username); | ||
| } catch { | ||
| // Ignore rollback errors | ||
| } | ||
| throw new APIError("Failed to create XMPP account record", 500); |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/app/api/xmpp/accounts/`[id]/route.ts:
- Around line 27-30: The GET handler uses an undeclared type
RouteContext<"/api/xmpp/accounts/[id]"> which causes a TS error; update the
function signature in src/app/api/xmpp/accounts/[id]/route.ts by either
importing RouteContext from Next.js (if your Next version exports it) or replace
the type with the standard Next.js route handler shape (e.g., accept NextRequest
and a params object with id: string or Promise<{ params: { id: string } }>),
updating the GET declaration accordingly so the compiler recognizes the context
parameter.
- Around line 14-19: Add a Zod schema for the route params (e.g., paramsSchema =
z.object({ id: z.string().nonempty() })) and use paramsSchema.parse(ctx.params)
at the start of each handler (GET/PATCH/DELETE) in route.ts before any DB
access; replace direct uses of ctx.params.id with the validated value
(parsed.id) and ensure handlers return a 400/422 on parse errors per existing
error-handling conventions. This touches the same file alongside
updateXmppAccountSchema and ensures ctx.params.id is validated uniformly in each
handler.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/app/api/xmpp/accounts/[id]/route.ts
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)
**/*.{js,jsx,ts,tsx}: UseSentry.captureException(error)to capture exceptions and log errors in Sentry, particularly in try-catch blocks or areas where exceptions are expected
UseSentry.startSpan()function to create spans for meaningful actions within applications like button clicks, API calls, and function calls
Files:
src/app/api/xmpp/accounts/[id]/route.ts
**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)
When creating custom spans for API calls with
Sentry.startSpan(), ensure thenameandopproperties are meaningful (e.g.,op: "http.client"with descriptive names likeGET /api/users/${userId}), and attach attributes based on relevant request information and metrics
Files:
src/app/api/xmpp/accounts/[id]/route.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)
**/*.{js,ts,jsx,tsx}: Import Sentry usingimport * as Sentry from "@sentry/nextjs"when using logs in NextJS projects
Reference the Sentry logger usingconst { logger } = Sentrywhen using logging functionality
Uselogger.fmtas a template literal function to bring variables into structured logs, providing better log formatting and variable interpolation
Files:
src/app/api/xmpp/accounts/[id]/route.ts
**/*.{css,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)
Use container query support with
@container,@sm:,@md:for container-based breakpoints and@max-md:for max-width queries
Files:
src/app/api/xmpp/accounts/[id]/route.ts
**/*.{ts,tsx,js,jsx,html}
📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)
**/*.{ts,tsx,js,jsx,html}: Use 3D transform utilities:transform-3d,rotate-x-*,rotate-y-*,rotate-z-*,scale-z-*,translate-z-*,perspective-*, andperspective-origin-*
Use linear gradient angles withbg-linear-45syntax and gradient interpolation likebg-linear-to-r/oklchorbg-linear-to-r/srgb
Use conic and radial gradients withbg-conicandbg-radial-[at_25%_25%]utilities
Useinset-shadow-*andinset-ring-*utilities instead of deprecated shadow opacity utilities
Usefield-sizing-contentutility for auto-resizing textareas
Usescheme-lightandscheme-darkutilities forcolor-schemeproperty
Usefont-stretch-*utilities for variable font configuration
Chain variants together for composable variants (e.g.,group-has-data-potato:opacity-100)
Usestartingvariant for@starting-styletransitions
Usenot-*variant for:not()pseudo-class (e.g.,not-first:mb-4)
Useinertvariant for styling elements with theinertattribute
Usenth-*variants:nth-3:,nth-last-5:,nth-of-type-4:,nth-last-of-type-6:for targeting specific elements
Usein-*variant as a simpler alternative togroup-*without addinggroupclass
Useopenvariant to support:popover-openpseudo-class
Use**variant for targeting all descendants
Replace deprecatedbg-opacity-*utilities with color values using slash notation (e.g.,bg-black/50)
Replace deprecatedtext-opacity-*utilities with color values using slash notation (e.g.,text-black/50)
Replace deprecatedborder-opacity-*,divide-opacity-*and similar opacity utilities with color slash notation
Useshadow-xsinstead ofshadow-smandshadow-sminstead ofshadow
Usedrop-shadow-xsinstead ofdrop-shadow-smanddrop-shadow-sminstead ofdrop-shadow
Useblur-xsinstead ofblur-smandblur-sminstead ofblur
Userounded-xsinstead ofrounded-smandrounded-sminstead ofrounded
Useoutline-hiddeninstead ofoutline-nonefor...
Files:
src/app/api/xmpp/accounts/[id]/route.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)
**/*.{ts,tsx}: Create QueryClient using getQueryClient() function that automatically handles server/client isolation (new instance per server request, singleton on client)
Always use the centralized query key factory from src/lib/api/query-keys.ts instead of creating keys manually
Include all variables used in queryFn in the query key to ensure proper cache management and query invalidation
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
**/*.{ts,tsx}: Use TypeScript strict mode
Use functional components and hooks in React
Prefer composition over inheritance
Use selective imports over barrel exports for performance
Write accessible, performant, type-safe code
Use explicit types for clarity, preferunknownoverany
Always await promises in async functions
Use semantic HTML and ARIA attributes for accessibility
Use@/authmodule for all authentication operations
Use@/dbfor all database operations
Use@/components/ui/*for importing shadcn/ui components with direct imports
Validate all inputs with Zod schemas
Implement proper RBAC with permissions module
Use TypeScript paths for imports configured in tsconfig.json
Implement proper error boundaries in React
Use Suspense for loading states
Optimize images with next/image
Use TanStack Query for all server state management
Files:
src/app/api/xmpp/accounts/[id]/route.ts
{src/app/**,src/components/**}/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)
{src/app/**,src/components/**}/*.{ts,tsx}: Use useUsers(), useUser(), useSessions(), useAdminStats(), useUpdateUser(), useDeleteUser(), useDeleteSession() hooks from src/hooks/use-admin.ts for standard queries
Use corresponding Suspense hooks (useUsersSuspense(), useUserSuspense(), etc.) from src/hooks/use-admin-suspense.ts when wrapping components in Suspense and Error Boundaries
Files:
src/app/api/xmpp/accounts/[id]/route.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{ts,tsx,js,jsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Use function components over class components
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use thekeyprop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props in React components
Don't define components inside other components
Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, and use semantic elements instead of divs with roles
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Group related code together and separate concerns
Add `...
Files:
src/app/api/xmpp/accounts/[id]/route.ts
src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/app/**/*.{ts,tsx}: Follow Next.js App Router conventions
Never expose API keys in client code
Files:
src/app/api/xmpp/accounts/[id]/route.ts
src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Handle internationalization via next-intl with locale files in
locale/directory
Files:
src/app/api/xmpp/accounts/[id]/route.ts
🧠 Learnings (12)
📚 Learning: 2025-12-18T18:18:05.202Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/anti-slop.mdc:0-0
Timestamp: 2025-12-18T18:18:05.202Z
Learning: Applies to **/api/**/*.{ts,tsx,js,jsx} : API, DB, auth contracts, or tokens require mandatory review
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2025-12-18T18:18:05.202Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/anti-slop.mdc:0-0
Timestamp: 2025-12-18T18:18:05.202Z
Learning: Applies to **/*route*.{ts,tsx,js,jsx} : Clean, non-duplicated REST routes in backend code
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx} : Use explicit types for function parameters and return values when they enhance clarity
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `async/await` syntax instead of promise chains for better readability
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to src/app/**/*.{ts,tsx} : Follow Next.js App Router conventions
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Always await promises in async functions
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Always `await` promises in async functions - don't forget to use the return value
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2025-12-18T18:18:05.202Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/anti-slop.mdc:0-0
Timestamp: 2025-12-18T18:18:05.202Z
Learning: Applies to **/backend/**/*.{ts,tsx,js,jsx} : Mandatory Zod validation at boundaries in backend
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Validate all inputs with Zod schemas
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : Prefetch only critical data that users will definitely see, using Promise.all() for parallel prefetching instead of sequential await
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to {src/app/**,src/components/**}/*.{tsx} : In regular query hooks, check isPending first, then isError, then use data to ensure TypeScript type narrowing works correctly
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : Perform auth checks using Better Auth's auth.api.getSession() in Server Components before prefetching data, and redirect if not authenticated
Applied to files:
src/app/api/xmpp/accounts/[id]/route.ts
🧬 Code graph analysis (1)
src/app/api/xmpp/accounts/[id]/route.ts (4)
src/lib/api/utils.ts (3)
requireAuth(69-82)handleAPIError(103-125)APIError(87-97)src/lib/db/schema/xmpp.ts (1)
xmppAccount(19-43)src/lib/auth/check-role.ts (1)
isAdmin(46-49)src/lib/xmpp/client.ts (2)
deleteProsodyAccount(145-184)ProsodyAccountNotFoundError(10-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Seer Code Review
- GitHub Check: Sourcery review
🔇 Additional comments (2)
src/app/api/xmpp/accounts/[id]/route.ts (2)
127-138: Good guardrail against username changes.Explicitly blocking username updates avoids DB/Prosody drift and keeps auth consistent.
223-241: Prosody deletion error handling looks solid.The
ProsodyAccountNotFoundErrortolerance with explicit handling for other errors is clear and safe.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| // Zod schema for update request validation | ||
| const updateXmppAccountSchema = z.object({ | ||
| username: z.string().optional(), | ||
| status: z.enum(["active", "suspended", "deleted"]).optional(), | ||
| metadata: z.record(z.string(), z.unknown()).nullable().optional(), | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n src/app/api/xmpp/accounts/[id]/route.tsRepository: allthingslinux/portal
Length of output: 8787
🏁 Script executed:
# Search for existing param schemas in the codebase
rg "ParamsSchema|paramsSchema|params.*z\.object" --type ts --type tsx -A 2Repository: allthingslinux/portal
Length of output: 92
🏁 Script executed:
# Look for similar route files to see validation patterns
fd "route\.ts" src/app/api --exec head -30 {} \; 2>/dev/null | head -100Repository: allthingslinux/portal
Length of output: 3455
🏁 Script executed:
# Search for RouteContext type definition
rg "type RouteContext|RouteContext.*=" --type ts -B 2 -A 2Repository: allthingslinux/portal
Length of output: 47
🏁 Script executed:
# Check if param schemas are shared across routes
rg "ParamsSchema|paramsSchema" --type ts -B 2 -A 2Repository: allthingslinux/portal
Length of output: 47
🏁 Script executed:
# Look for any global or utility param validation
fd "params" src/lib --type ts | head -20Repository: allthingslinux/portal
Length of output: 234
🏁 Script executed:
# Search for RouteContext in entire codebase
rg "RouteContext" --type-listRepository: allthingslinux/portal
Length of output: 454
🏁 Script executed:
# Search for RouteContext definition
rg "type RouteContext|interface RouteContext"Repository: allthingslinux/portal
Length of output: 47
🏁 Script executed:
# Check if there are param validation utilities
find src -name "*.ts" -o -name "*.tsx" | xargs grep -l "params.*parse\|ParamsSchema" 2>/dev/null | head -10Repository: allthingslinux/portal
Length of output: 105
🏁 Script executed:
# Search for RouteContext definition more directly
rg "RouteContext" -A 3Repository: allthingslinux/portal
Length of output: 3288
🏁 Script executed:
# Check what's in lib/api to understand utilities
ls -la src/lib/api/Repository: allthingslinux/portal
Length of output: 791
🏁 Script executed:
# Look for any param schema examples
rg "z\.object.*id" src/app/api -B 2 -A 2Repository: allthingslinux/portal
Length of output: 47
🏁 Script executed:
# Check tsconfig for type definitions
cat tsconfig.json | grep -A 20 "types\|compilerOptions"Repository: allthingslinux/portal
Length of output: 765
🏁 Script executed:
# Look for globals.d.ts or similar files
find src -name "*.d.ts" -o -name "globals.ts"Repository: allthingslinux/portal
Length of output: 47
🏁 Script executed:
# Search in node_modules or type definitions
rg "type RouteContext" --type-add 'ts:*.ts:*.tsx' -l 2>/dev/null | head -5Repository: allthingslinux/portal
Length of output: 47
🏁 Script executed:
# Check Next.js types - RouteContext might be from Next.js itself
grep -r "RouteContext" . --include="*.ts" --include="*.tsx" | grep "import\|export type" | head -10Repository: allthingslinux/portal
Length of output: 47
🏁 Script executed:
# Check the actual imports in the file we're reviewing
head -20 src/app/api/xmpp/accounts/[id]/route.tsRepository: allthingslinux/portal
Length of output: 731
🏁 Script executed:
# Search for RouteContext everywhere (including comments, exports, etc)
rg "RouteContext" . 2>/dev/null | wc -lRepository: allthingslinux/portal
Length of output: 67
🏁 Script executed:
# Check another similar route file to see if RouteContext is imported
head -30 src/app/api/admin/users/[id]/route.tsRepository: allthingslinux/portal
Length of output: 894
🏁 Script executed:
# Check if RouteContext is a Next.js global by looking at node_modules type definitions
grep -r "RouteContext" node_modules/@types/next 2>/dev/null | head -5Repository: allthingslinux/portal
Length of output: 47
Validate route params (id) with Zod before DB access.
id is used directly from ctx.params in all handlers (GET/PATCH/DELETE) without validation. Per backend boundary validation guidelines, add a Zod schema for route params and parse them before hitting the database. This prevents malformed inputs and keeps validation consistent across all methods.
Proposed fix (apply to GET/PATCH/DELETE)
+const xmppAccountParamsSchema = z.object({
+ id: z.string().min(1),
+})
+
// Zod schema for update request validation
const updateXmppAccountSchema = z.object({
username: z.string().optional(),
status: z.enum(["active", "suspended", "deleted"]).optional(),
metadata: z.record(z.string(), z.unknown()).nullable().optional(),
});- const { id } = await ctx.params;
+ const { id } = xmppAccountParamsSchema.parse(await ctx.params);🤖 Prompt for AI Agents
In `@src/app/api/xmpp/accounts/`[id]/route.ts around lines 14 - 19, Add a Zod
schema for the route params (e.g., paramsSchema = z.object({ id:
z.string().nonempty() })) and use paramsSchema.parse(ctx.params) at the start of
each handler (GET/PATCH/DELETE) in route.ts before any DB access; replace direct
uses of ctx.params.id with the validated value (parsed.id) and ensure handlers
return a 400/422 on parse errors per existing error-handling conventions. This
touches the same file alongside updateXmppAccountSchema and ensures
ctx.params.id is validated uniformly in each handler.
| export async function GET( | ||
| request: NextRequest, | ||
| ctx: RouteContext<"/api/xmpp/accounts/[id]"> | ||
| ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for RouteContext definition and usage
echo "=== Searching for RouteContext definitions/imports ==="
rg -n "RouteContext" --type=ts --type=tsx
echo -e "\n=== Checking next.config files for typedRoutes ==="
fd "next.config.*" -x cat {}Repository: allthingslinux/portal
Length of output: 14341
🏁 Script executed:
#!/bin/bash
# Check the actual route file
echo "=== Viewing route file ==="
cat -n "src/app/api/xmpp/accounts/[id]/route.ts" | head -40
echo -e "\n=== Checking tsconfig.json for type definitions ==="
cat tsconfig.jsonRepository: allthingslinux/portal
Length of output: 2510
🏁 Script executed:
#!/bin/bash
# Search more broadly for RouteContext in all file types
echo "=== Searching for RouteContext definition in all files ==="
rg "RouteContext" -g "!node_modules"
echo -e "\n=== Checking if there's a types file for custom route context ==="
fd "types\|index" --type f -x grep -l "RouteContext" {} \;Repository: allthingslinux/portal
Length of output: 1254
Add import for RouteContext or replace with standard Next.js types.
The file uses RouteContext<"/api/xmpp/accounts/[id]"> without importing it. This will cause a TypeScript error since RouteContext is not defined in the codebase (verified across all route files using this pattern). Either import this type from Next.js if available, define it locally, or replace it with the standard Next.js route handler signature using { params: Promise<{...}> }.
🤖 Prompt for AI Agents
In `@src/app/api/xmpp/accounts/`[id]/route.ts around lines 27 - 30, The GET
handler uses an undeclared type RouteContext<"/api/xmpp/accounts/[id]"> which
causes a TS error; update the function signature in
src/app/api/xmpp/accounts/[id]/route.ts by either importing RouteContext from
Next.js (if your Next version exports it) or replace the type with the standard
Next.js route handler shape (e.g., accept NextRequest and a params object with
id: string or Promise<{ params: { id: string } }>), updating the GET declaration
accordingly so the compiler recognizes the context parameter.
- Introduced a new module for validated XMPP environment variables using t3-env, improving type safety and runtime validation. - Updated the XMPP configuration to utilize the new keys module for environment variable management. - Implemented error capturing to Sentry for configuration validation, ensuring early detection of missing environment variables. - Enhanced observability by integrating Sentry error handling in the XMPP configuration validation process. - Removed deprecated observability utilities and consolidated them into a new helpers module for better organization and maintainability.
| .update(xmppAccount) | ||
| .set({ | ||
| status: "deleted", | ||
| updatedAt: new Date(), | ||
| }) | ||
| .where(eq(xmppAccount.id, id)) | ||
| .returning(); | ||
|
|
||
| if (!deleted) { | ||
| throw new APIError("Failed to delete XMPP account", 500); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: The DELETE endpoint lacks a rollback mechanism. If the database update fails after the Prosody account is deleted, the system state becomes inconsistent.
Severity: MEDIUM
Suggested Fix
Wrap the database update and Prosody deletion in a way that ensures atomicity. Implement a rollback mechanism similar to the one in the POST endpoint: if the database update fails, attempt to restore the Prosody account. Alternatively, perform the database update first before deleting from the external service.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/app/api/xmpp/accounts/[id]/route.ts#L243-L255
Potential issue: In the DELETE endpoint, an account is first deleted from the external
Prosody service, and then its status is updated in the local database. If the database
update operation fails due to an error (e.g., connection issue, deadlock), an exception
is caught and a 500 error is returned. However, the Prosody account has already been
deleted, and there is no rollback mechanism to recreate it. This leaves the system in an
inconsistent state where the account is gone from Prosody but still appears as 'active'
in the database and UI.
Did we get this right? 👍 / 👎 to inform future reviews.
| const availabilityResult = await checkUsernameAvailability(username); | ||
| if (!availabilityResult.available) { | ||
| return availabilityResult.error; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: A race condition during account creation can lead to an orphaned Prosody account because the rollback logic is not triggered on a database constraint violation.
Severity: HIGH
Suggested Fix
Wrap the database insertion in a try...catch block. In the catch block, check if the error is a database unique constraint violation. If it is, execute the rollback logic to delete the orphaned Prosody account. This ensures that failed account creations due to race conditions are properly cleaned up.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/app/api/xmpp/accounts/route.ts#L164-L167
Potential issue: A race condition can occur when two concurrent requests attempt to
create an XMPP account with the same username. Both requests can pass the initial
availability check and create an account in the external Prosody service. The first
request will succeed in creating the database record. The second request's database
insert will fail with a unique constraint violation, throwing an exception. The existing
error handling logic does not catch this specific exception type, so the rollback code
designed to delete the orphaned Prosody account is never executed, leaving an orphaned
account in Prosody.
Did we get this right? 👍 / 👎 to inform future reviews.
Summary by Sourcery
Add end-to-end XMPP account management support, including OAuth scope exposure, API endpoints, database schema, and a user-facing settings page.
New Features: