diff --git a/src/core/ConfigManager.ts b/src/core/ConfigManager.ts index 9ef3b82..d23c0db 100644 --- a/src/core/ConfigManager.ts +++ b/src/core/ConfigManager.ts @@ -4,7 +4,7 @@ import { z } from "zod"; export const ProviderConfigSchema = z.object({ clientId: z.string(), clientSecret: z.string(), - redirectUri: z.string().url(), + redirectUri: z.string().url().optional(), authUrl: z.string().url(), tokenUrl: z.string().url(), scope: z.string(), diff --git a/src/providers/GenericProvider.ts b/src/providers/GenericProvider.ts index d7b6339..3f3dfdc 100644 --- a/src/providers/GenericProvider.ts +++ b/src/providers/GenericProvider.ts @@ -14,17 +14,18 @@ export class GenericProvider implements OAuthProvider { private config: ProviderConfig, ) {} - getAuthUrl(): string { + getAuthUrl(state: string, redirectUri: string): string { const params = new URLSearchParams({ response_type: "code", client_id: this.config.clientId, - redirect_uri: this.config.redirectUri, + redirect_uri: redirectUri, scope: this.config.scope, + state, }); return `${this.config.authUrl}?${params.toString()}`; } - async exchangeCode(code: string): Promise { + async exchangeCode(code: string, redirectUri: string): Promise { const response = await fetch(this.config.tokenUrl, { method: "POST", headers: { @@ -35,7 +36,7 @@ export class GenericProvider implements OAuthProvider { code, client_id: this.config.clientId, client_secret: this.config.clientSecret, - redirect_uri: this.config.redirectUri, + redirect_uri: redirectUri, grant_type: "authorization_code", }), }); diff --git a/src/providers/interface.ts b/src/providers/interface.ts index 27b2e22..6549897 100644 --- a/src/providers/interface.ts +++ b/src/providers/interface.ts @@ -6,7 +6,7 @@ export interface TokenResponse { export interface OAuthProvider { name: string; - getAuthUrl(): string; - exchangeCode(code: string): Promise; + getAuthUrl(state: string, redirectUri: string): string; + exchangeCode(code: string, redirectUri: string): Promise; refreshToken(refreshToken: string): Promise; } diff --git a/src/routes/auth.ts b/src/routes/auth.ts index 81c057f..24463e1 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -15,25 +15,29 @@ authRoutes.get("/:provider/login", async (c) => { return c.json( { error: "Configuration Not Found", - message: `Provider '${providerName}' is not configured in the registry. Please add it via the dashboard first.`, + message: `Provider '${providerName}' is not configured.`, }, 404, ); } const provider = new GenericProvider(providerName, providerConfig); - return c.redirect(provider.getAuthUrl()); + + const url = new URL(c.req.url); + const redirectUri = providerConfig.redirectUri || `${url.origin}/auth/callback`; + + return c.redirect(provider.getAuthUrl(providerName, redirectUri)); }); -authRoutes.get("/:provider/callback", async (c) => { - const providerName = c.req.param("provider"); +authRoutes.get("/callback", async (c) => { + const providerName = c.req.query("state"); const code = c.req.query("code"); - if (!code) { + if (!providerName || !code) { return c.json( { - error: "Authorization Code Missing", - message: "The provider did not return an authorization code.", + error: "Invalid Request", + message: "Missing state (provider) or authorization code.", }, 400, ); @@ -46,17 +50,20 @@ authRoutes.get("/:provider/callback", async (c) => { return c.json( { error: "Configuration Not Found", - message: `Provider '${providerName}' was configured during login but its configuration is missing now.`, + message: `Provider '${providerName}' is not configured.`, }, 404, ); } + const url = new URL(c.req.url); + const redirectUri = providerConfig.redirectUri || `${url.origin}/auth/callback`; + const provider = new GenericProvider(providerName, providerConfig); const tokenManager = new TokenManager(redis, provider); try { - const tokens = await provider.exchangeCode(code); + const tokens = await provider.exchangeCode(code, redirectUri); await tokenManager.saveTokens(providerName, tokens); return c.html(` @@ -76,7 +83,6 @@ authRoutes.get("/:provider/callback", async (c) => {

Authenticated!

Successfully connected to ${providerName}.

-

You can now safely close this window and return to the dashboard.

@@ -86,10 +92,7 @@ authRoutes.get("/:provider/callback", async (c) => { `); } catch (error) { - const errorMessage = - error instanceof Error - ? error.message - : "An unexpected error occurred during token exchange."; + const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred."; console.error(`[OAuth Error] ${errorMessage}`); return c.json( { diff --git a/src/views/dashboard.html b/src/views/dashboard.html index af8b8e0..a279610 100644 --- a/src/views/dashboard.html +++ b/src/views/dashboard.html @@ -10,14 +10,8 @@ @@ -25,12 +19,10 @@