-
-
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
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
ebcf263
feat(xmpp): implement Prosody REST API client for account management
kzndotsh 40da294
feat(routes): add XMPP route configuration
kzndotsh 73ebb52
feat(xmpp): add XMPP account schema definition
kzndotsh 23cb8dc
feat(auth): enhance OAuth configuration to support XMPP scope
kzndotsh 47b665c
feat(xmpp): add XMPP API client functions and query keys
kzndotsh 61d996e
feat(xmpp): implement hooks for XMPP account management
kzndotsh 5836c90
feat(xmpp): add XMPP account management component
kzndotsh 4b50efd
feat(xmpp): implement XMPP account API routes
kzndotsh 60febec
feat(xmpp): create XMPP account management page
kzndotsh ad50012
feat(oauth): add script to create Prosody OAuth client
kzndotsh c7b7148
chore(vscode): update default Markdown formatter in settings
kzndotsh 36f749c
feat(xmpp): enhance XMPP account management and validation
kzndotsh 65e51a6
refactor(xmpp): update route parameter handling in account API
kzndotsh 9b83af7
feat(xmpp): enhance XMPP configuration and observability integration
kzndotsh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,4 +40,5 @@ yarn-error.log* | |
| next-env.d.ts | ||
| references | ||
|
|
||
| .cursor/plans | ||
| .cursor/plans | ||
| .cursor/hooks/logs/ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| import "dotenv/config"; | ||
|
|
||
| import { randomBytes, randomUUID } from "node:crypto"; | ||
| import { eq } from "drizzle-orm"; | ||
|
|
||
| import { db } from "@/lib/db"; | ||
| import { oauthClient } from "@/lib/db/schema/oauth"; | ||
|
|
||
| // ============================================================================ | ||
| // Create Prosody OAuth Client Script | ||
| // ============================================================================ | ||
| // This script registers Prosody XMPP server as an OAuth client in Better Auth. | ||
| // It creates a confidential client with password grant type for legacy XMPP | ||
| // client support. | ||
| // | ||
| // Usage: | ||
| // pnpm create-prosody-oauth-client | ||
| // | ||
| // Environment Variables: | ||
| // PROSODY_CLIENT_NAME - Name for the OAuth client (default: "Prosody XMPP Server") | ||
| // PROSODY_CLIENT_ID - Custom client ID (optional, auto-generated if not provided) | ||
| // | ||
| // Output: | ||
| // Prints the client_id and client_secret that should be set in Prosody | ||
| // configuration as PROSODY_OAUTH_CLIENT_ID and PROSODY_OAUTH_CLIENT_SECRET | ||
|
|
||
| async function createProsodyOAuthClient() { | ||
| const clientName = process.env.PROSODY_CLIENT_NAME || "Prosody XMPP Server"; | ||
| const customClientId = process.env.PROSODY_CLIENT_ID; | ||
|
|
||
| try { | ||
| // Check if Prosody client already exists | ||
| const existingClient = await db | ||
| .select() | ||
| .from(oauthClient) | ||
| .where(eq(oauthClient.name, clientName)) | ||
| .limit(1); | ||
|
|
||
| if (existingClient.length > 0) { | ||
| const client = existingClient[0]; | ||
| console.log("ℹ️ Prosody OAuth client already exists:"); | ||
| console.log(" Client ID:", client.clientId); | ||
| console.log( | ||
| " Client Secret:", | ||
| client.clientSecret ? "(set - not displayed for security)" : "(not set)" | ||
| ); | ||
| console.log(" Name:", client.name); | ||
| console.log(" Disabled:", client.disabled); | ||
| return; | ||
| } | ||
|
|
||
| // Generate client ID and secret | ||
| // Use randomBytes for secure random generation | ||
| const generateRandomString = (length: number) => | ||
| randomBytes(length).toString("base64url").slice(0, length); | ||
| const clientId = customClientId || `prosody_${generateRandomString(32)}`; | ||
| const clientSecret = generateRandomString(64); | ||
|
|
||
| // Create OAuth client in database | ||
| 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, | ||
| }) | ||
| .returning(); | ||
|
|
||
| if (!newClient) { | ||
| throw new Error("Failed to create OAuth client: No client returned"); | ||
| } | ||
|
|
||
| console.log("✅ Prosody OAuth client created successfully!"); | ||
| console.log(""); | ||
| console.log("📋 Configuration for Prosody:"); | ||
| console.log(` PROSODY_OAUTH_CLIENT_ID=${newClient.clientId}`); | ||
| console.log(` PROSODY_OAUTH_CLIENT_SECRET=${newClient.clientSecret}`); | ||
| console.log(""); | ||
| console.log("⚠️ Store these credentials securely!"); | ||
| console.log( | ||
| " Add them to your Prosody environment variables or .env file." | ||
| ); | ||
| } catch (error) { | ||
| console.error("❌ Failed to create Prosody OAuth client:"); | ||
| if (error instanceof Error) { | ||
| console.error(" Error:", error.message); | ||
| if (error.stack) { | ||
| console.error(" Stack:", error.stack); | ||
| } | ||
| } else { | ||
| console.error(" Error:", error); | ||
| } | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| // Run if called directly | ||
| if (require.main === module) { | ||
| createProsodyOAuthClient() | ||
| .then(() => process.exit(0)) | ||
| .catch((error) => { | ||
| console.error(error); | ||
| process.exit(1); | ||
| }); | ||
| } | ||
|
|
||
| export { createProsodyOAuthClient }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import type { Metadata } from "next"; | ||
| import { dehydrate, HydrationBoundary } from "@tanstack/react-query"; | ||
|
|
||
| import { PageHeader } from "@/components/layout/page/page-header"; | ||
| import { XmppAccountManagement } from "@/components/xmpp/xmpp-account-management"; | ||
| import { getServerQueryClient } from "@/lib/api/hydration"; | ||
| import { verifySession } from "@/lib/auth/dal"; | ||
| import { getServerRouteResolver, routeConfig } from "@/lib/routes"; | ||
| import { getRouteMetadata } from "@/lib/seo"; | ||
|
|
||
| // Metadata is automatically generated from route config | ||
| export async function generateMetadata(): Promise<Metadata> { | ||
| const resolver = await getServerRouteResolver(); | ||
| return getRouteMetadata("/app/xmpp", routeConfig, resolver); | ||
| } | ||
|
|
||
| // ============================================================================ | ||
| // XMPP Account Management Page | ||
| // ============================================================================ | ||
| // Page for managing XMPP accounts - create, view, update, and delete | ||
|
|
||
| export default async function XmppPage() { | ||
| // Verify user session | ||
| await verifySession(); | ||
|
|
||
| // Create QueryClient for this request (isolated per request) | ||
| const queryClient = getServerQueryClient(); | ||
|
|
||
| // Note: We don't prefetch XMPP account data here because: | ||
| // 1. It requires authentication cookies which aren't available server-side | ||
| // 2. Prefetching with null would mark the query as successful and block refetch for 30s | ||
| // The client-side hook will fetch the data when the component mounts | ||
|
|
||
| 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> | ||
| ); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
🏁 Script executed:
Repository: allthingslinux/portal
Length of output: 120
🏁 Script executed:
Repository: allthingslinux/portal
Length of output: 358
🏁 Script executed:
Repository: allthingslinux/portal
Length of output: 4827
Verify rate limiting and secure storage are implemented for the password grant flow.
The
passwordgrant 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:authorization_code+ PKCE flow once Prosody XMPP client support allows🤖 Prompt for AI Agents