core/apps/webapp/app/services/oauth/oauth-utils.server.ts
Harshith Mullapudi 54e535d57d
Feat: v2 (#12)
* Feat: v2

* feat: add chat functionality

* First cut: integrations

* Feat: add conversation API

* Enhance conversation handling and memory management

* Feat: added conversation

---------

Co-authored-by: Manoj K <saimanoj58@gmail.com>
2025-07-08 22:41:00 +05:30

156 lines
3.9 KiB
TypeScript

import { type OAuth2Params } from "@core/types";
import { IsBoolean, IsString } from "class-validator";
import type { IntegrationDefinitionV2 } from "@core/database";
import { z } from "zod";
export interface RedirectURLParams {
workspaceSlug: string;
integrationOAuthAppName: string;
config: string;
}
export interface SessionRecord {
integrationDefinitionId: string;
config: OAuth2Params;
redirectURL: string;
workspaceId: string;
accountIdentifier?: string;
integrationKeys?: string;
personal: boolean;
userId?: string;
}
export class OAuthBodyInterface {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
config?: any;
@IsString()
redirectURL: string;
@IsBoolean()
personal: boolean = false;
@IsString()
integrationDefinitionId: string;
}
export const OAuthBodySchema = z.object({
config: z.any().optional(),
redirectURL: z.string(),
personal: z.boolean().default(false),
integrationDefinitionId: z.string(),
});
export type CallbackParams = Record<string, string>;
export interface ProviderConfig {
client_id: string;
client_secret: string;
scopes: string;
}
const enum ProviderAuthModes {
"OAuth2" = "OAuth2",
}
export interface ProviderTemplate extends OAuth2Params {
auth_mode: ProviderAuthModes;
}
export enum OAuthAuthorizationMethod {
BODY = "body",
HEADER = "header",
}
export enum OAuthBodyFormat {
FORM = "form",
JSON = "json",
}
export interface ProviderTemplateOAuth2 extends ProviderTemplate {
auth_mode: ProviderAuthModes.OAuth2;
disable_pkce?: boolean; // Defaults to false (=PKCE used) if not provided
token_params?: {
grant_type?: "authorization_code" | "client_credentials";
};
refresh_params?: {
grant_type: "refresh_token";
};
authorization_method?: OAuthAuthorizationMethod;
body_format?: OAuthBodyFormat;
refresh_url?: string;
token_request_auth_method?: "basic";
}
/**
* A helper function to interpolate a string.
* interpolateString('Hello ${name} of ${age} years", {name: 'Tester', age: 234}) -> returns 'Hello Tester of age 234 years'
*
* @remarks
* Copied from https://stackoverflow.com/a/1408373/250880
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function interpolateString(str: string, replacers: Record<string, any>) {
return str.replace(/\${([^{}]*)}/g, (a, b) => {
const r = replacers[b];
return typeof r === "string" || typeof r === "number" ? (r as string) : a; // Typecast needed to make TypeScript happy
});
}
export function getSimpleOAuth2ClientConfig(
providerConfig: ProviderConfig,
template: ProviderTemplate,
connectionConfig: OAuth2Params,
) {
const tokenUrl = new URL(
interpolateString(template.token_url, connectionConfig),
);
const authorizeUrl = new URL(
interpolateString(template.authorization_url, connectionConfig),
);
const headers = { "User-Agent": "Sol" };
const authConfig = template as ProviderTemplateOAuth2;
return {
client: {
id: providerConfig.client_id,
secret: providerConfig.client_secret,
},
auth: {
tokenHost: tokenUrl.origin,
tokenPath: tokenUrl.pathname,
authorizeHost: authorizeUrl.origin,
authorizePath: authorizeUrl.pathname,
},
http: { headers },
options: {
authorizationMethod:
authConfig.authorization_method || OAuthAuthorizationMethod.BODY,
bodyFormat: authConfig.body_format || OAuthBodyFormat.FORM,
scopeSeparator: template.scope_separator || " ",
},
};
}
export async function getTemplate(
integrationDefinition: IntegrationDefinitionV2,
): Promise<ProviderTemplate> {
const spec = integrationDefinition.spec as any;
const template: ProviderTemplate = spec.auth.OAuth2 as ProviderTemplate;
if (!template) {
throw new Error(
`This extension doesn't support OAuth. Reach out to us if you need support for this extension`,
);
}
return template;
}