Skip to content
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

feat: make connectionId optional when creating a new connection #2746

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions packages/frontend/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export interface AuthResult {
isPending?: boolean;
}

interface AuthOptions {
type AuthOptions = {
detectClosedAuthWindow?: boolean; // If true, `nango.auth()` would fail if the login window is closed before the authorization flow is completed
}
} & (ConnectionConfig | OAuth2ClientCredentials | OAuthCredentialsOverride | BasicApiCredentials | ApiKeyCredentials | AppStoreCredentials);

export default class Nango {
private hostBaseUrl: string;
Expand Down Expand Up @@ -82,11 +82,24 @@ export default class Nango {
/**
* Creates a new unauthenticated connection using the specified provider configuration key and connection ID
* @param providerConfigKey - The key identifying the provider configuration on Nango
* @param connectionId - The ID of the connection
* @param connectionId - Optional. The ID of the connection
* @param connectionConfig - Optional. Additional configuration for the connection
* @returns A promise that resolves with the authentication result
*/
public async create(providerConfigKey: string, connectionId: string, connectionConfig?: ConnectionConfig): Promise<AuthResult> {
public async create(providerConfigKey: string, connectionConfig?: ConnectionConfig): Promise<AuthResult>;
public async create(providerConfigKey: string, connectionId: string, connectionConfig?: ConnectionConfig): Promise<AuthResult>;
public async create(
providerConfigKey: string,
connectionIdOrConnectionConfig?: string | ConnectionConfig,
moreConnectionConfig?: ConnectionConfig
): Promise<AuthResult> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

method overload to allow passing connectionConfig without having to specify an undefined connectionId

let connectionId: string | null = null;
let connectionConfig: ConnectionConfig | undefined = moreConnectionConfig;
if (typeof connectionIdOrConnectionConfig === 'string') {
connectionId = connectionIdOrConnectionConfig;
} else {
connectionConfig = connectionIdOrConnectionConfig;
}
const url = this.hostBaseUrl + `/auth/unauthenticated/${providerConfigKey}${this.toQueryString(connectionId, connectionConfig)}`;

const res = await fetch(url, {
Expand All @@ -107,16 +120,23 @@ export default class Nango {
/**
* Initiates the authorization process for a connection
* @param providerConfigKey - The key identifying the provider configuration on Nango
* @param connectionId - The ID of the connection for which to authorize
* @param connectionId - Optional. The ID of the connection for which to authorize
* @param options - Optional. Additional options for authorization
* @returns A promise that resolves with the authorization result
*/
public auth(
providerConfigKey: string,
connectionId: string,
options?: (ConnectionConfig | OAuth2ClientCredentials | OAuthCredentialsOverride | BasicApiCredentials | ApiKeyCredentials | AppStoreCredentials) &
AuthOptions
): Promise<AuthResult> {
public auth(providerConfigKey: string, options?: AuthOptions): Promise<AuthResult>;
public auth(providerConfigKey: string, connectionId: string, options?: AuthOptions): Promise<AuthResult>;
public auth(providerConfigKey: string, connectionIdOrOptions?: string | AuthOptions, moreOptions?: AuthOptions): Promise<AuthResult> {
let connectionId: string | null = null;
let options: AuthOptions | undefined = moreOptions;
if (typeof connectionIdOrOptions === 'string') {
connectionId = connectionIdOrOptions;
} else {
options = {
...options,
...connectionIdOrOptions
};
}
if (
options &&
'credentials' in options &&
Expand Down Expand Up @@ -286,7 +306,7 @@ export default class Nango {
*/
private async customAuth(
providerConfigKey: string,
connectionId: string,
connectionId: string | null,
connectionConfigWithCredentials: ConnectionConfig,
connectionConfig?: ConnectionConfig
): Promise<AuthResult> {
Expand Down Expand Up @@ -430,7 +450,7 @@ export default class Nango {
* @param connectionConfig - Optional. Additional configuration for the connection
* @returns The generated query string
*/
private toQueryString(connectionId: string, connectionConfig?: ConnectionConfig): string {
private toQueryString(connectionId: string | null, connectionConfig?: ConnectionConfig): string {
const query: string[] = [];

if (connectionId) {
Expand Down
32 changes: 12 additions & 20 deletions packages/server/lib/controllers/apiAuth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ApiAuthController {
async apiKey(req: Request, res: Response<any, Required<RequestLocals>>, next: NextFunction) {
const { account, environment } = res.locals;
const { providerConfigKey } = req.params;
const connectionId = req.query['connection_id'] as string | undefined;
const receivedConnectionId = req.query['connection_id'] as string | undefined;
const connectionConfig = req.query['params'] != null ? getConnectionConfig(req.query['params']) : {};

let logCtx: LogContext | undefined;
Expand All @@ -48,12 +48,6 @@ class ApiAuthController {
return;
}

if (!connectionId) {
errorManager.errRes(res, 'missing_connection_id');

return;
}

const hmacEnabled = await hmacService.isEnabled(environment.id);
if (hmacEnabled) {
const hmac = req.query['hmac'] as string | undefined;
Expand All @@ -65,7 +59,7 @@ class ApiAuthController {

return;
}
const verified = await hmacService.verify(hmac, environment.id, providerConfigKey, connectionId);
const verified = await hmacService.verify(hmac, environment.id, providerConfigKey, receivedConnectionId);
if (!verified) {
await logCtx.error('Invalid HMAC');
await logCtx.failed();
Expand All @@ -76,6 +70,8 @@ class ApiAuthController {
}
}

const connectionId = receivedConnectionId || connectionService.generateConnectionId();

const config = await configService.getProviderConfig(providerConfigKey, environment.id);

if (config == null) {
Expand Down Expand Up @@ -176,7 +172,7 @@ class ApiAuthController {
if (logCtx) {
void connectionCreationFailedHook(
{
connection: { connection_id: connectionId!, provider_config_key: providerConfigKey! },
connection: { connection_id: receivedConnectionId!, provider_config_key: providerConfigKey! },
environment,
account,
auth_mode: 'API_KEY',
Expand All @@ -199,7 +195,7 @@ class ApiAuthController {
environmentId: environment.id,
metadata: {
providerConfigKey,
connectionId
receivedConnectionId
}
});

Expand All @@ -210,7 +206,7 @@ class ApiAuthController {
async basic(req: Request, res: Response<any, Required<RequestLocals>>, next: NextFunction) {
const { account, environment } = res.locals;
const { providerConfigKey } = req.params;
const connectionId = req.query['connection_id'] as string | undefined;
const receivedConnectionId = req.query['connection_id'] as string | undefined;
const connectionConfig = req.query['params'] != null ? getConnectionConfig(req.query['params']) : {};

let logCtx: LogContext | undefined;
Expand All @@ -232,12 +228,6 @@ class ApiAuthController {
return;
}

if (!connectionId) {
errorManager.errRes(res, 'missing_connection_id');

return;
}

const hmacEnabled = await hmacService.isEnabled(environment.id);
if (hmacEnabled) {
const hmac = req.query['hmac'] as string | undefined;
Expand All @@ -249,7 +239,7 @@ class ApiAuthController {

return;
}
const verified = await hmacService.verify(hmac, environment.id, providerConfigKey, connectionId);
const verified = await hmacService.verify(hmac, environment.id, providerConfigKey, receivedConnectionId);
if (!verified) {
await logCtx.error('Invalid HMAC');
await logCtx.failed();
Expand All @@ -259,6 +249,8 @@ class ApiAuthController {
}
}

const connectionId = receivedConnectionId || connectionService.generateConnectionId();

const { username = '', password = '' } = req.body;

const config = await configService.getProviderConfig(providerConfigKey, environment.id);
Expand Down Expand Up @@ -354,7 +346,7 @@ class ApiAuthController {
if (logCtx) {
void connectionCreationFailedHook(
{
connection: { connection_id: connectionId!, provider_config_key: providerConfigKey! },
connection: { connection_id: receivedConnectionId!, provider_config_key: providerConfigKey! },
environment,
account,
auth_mode: 'API_KEY',
Expand All @@ -377,7 +369,7 @@ class ApiAuthController {
environmentId: environment.id,
metadata: {
providerConfigKey,
connectionId
connectionId: receivedConnectionId
}
});

Expand Down
15 changes: 5 additions & 10 deletions packages/server/lib/controllers/appAuth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,16 @@ class AppAuthController {

void analytics.track(AnalyticsTypes.PRE_APP_AUTH, account.id);

const { providerConfigKey, connectionId, webSocketClientId: wsClientId } = session;
const { providerConfigKey, connectionId: receivedConnectionId, webSocketClientId: wsClientId } = session;
const logCtx = await logContextGetter.get({ id: session.activityLogId });

try {
if (!providerConfigKey) {
errorManager.errRes(res, 'missing_connection');

return;
}

if (!connectionId) {
errorManager.errRes(res, 'missing_connection_id');

return;
}
const connectionId = receivedConnectionId || connectionService.generateConnectionId();

const config = await configService.getProviderConfig(providerConfigKey, environment.id);

Expand Down Expand Up @@ -226,12 +221,12 @@ class AppAuthController {
await telemetry.log(LogTypes.AUTH_TOKEN_REQUEST_FAILURE, `App auth request process failed ${content}`, LogActionEnum.AUTH, {
environmentId: String(environment.id),
providerConfigKey: String(providerConfigKey),
connectionId: String(connectionId)
connectionId: String(receivedConnectionId)
});

void connectionCreationFailedHook(
{
connection: { connection_id: connectionId, provider_config_key: providerConfigKey },
connection: { connection_id: receivedConnectionId, provider_config_key: providerConfigKey },
environment,
account,
auth_mode: 'APP',
Expand All @@ -245,7 +240,7 @@ class AppAuthController {
logCtx
);

return publisher.notifyErr(res, wsClientId, providerConfigKey, connectionId, WSErrBuilder.UnknownError(prettyError));
return publisher.notifyErr(res, wsClientId, providerConfigKey, receivedConnectionId, WSErrBuilder.UnknownError(prettyError));
}
}
}
Expand Down
16 changes: 6 additions & 10 deletions packages/server/lib/controllers/appStoreAuth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class AppStoreAuthController {
async auth(req: Request, res: Response<any, Required<RequestLocals>>, next: NextFunction) {
const { environment, account } = res.locals;
const { providerConfigKey } = req.params;
const connectionId = req.query['connection_id'] as string | undefined;
const receivedConnectionId = req.query['connection_id'] as string | undefined;

let logCtx: LogContext | undefined;

Expand All @@ -42,12 +42,6 @@ class AppStoreAuthController {
return;
}

if (!connectionId) {
errorManager.errRes(res, 'missing_connection_id');

return;
}

const hmacEnabled = await hmacService.isEnabled(environment.id);
if (hmacEnabled) {
const hmac = req.query['hmac'] as string | undefined;
Expand All @@ -59,7 +53,7 @@ class AppStoreAuthController {

return;
}
const verified = await hmacService.verify(hmac, environment.id, providerConfigKey, connectionId);
const verified = await hmacService.verify(hmac, environment.id, providerConfigKey, receivedConnectionId);
if (!verified) {
await logCtx.error('Invalid HMAC');
await logCtx.failed();
Expand All @@ -70,6 +64,8 @@ class AppStoreAuthController {
}
}

const connectionId = receivedConnectionId || connectionService.generateConnectionId();

const config = await configService.getProviderConfig(providerConfigKey, environment.id);

if (config == null) {
Expand Down Expand Up @@ -185,7 +181,7 @@ class AppStoreAuthController {

void connectionCreationFailedHook(
{
connection: { connection_id: connectionId!, provider_config_key: providerConfigKey! },
connection: { connection_id: receivedConnectionId!, provider_config_key: providerConfigKey! },
environment,
account,
auth_mode: 'APP_STORE',
Expand All @@ -209,7 +205,7 @@ class AppStoreAuthController {
environmentId: environment.id,
metadata: {
providerConfigKey,
connectionId
connectionId: receivedConnectionId
}
});

Expand Down
12 changes: 7 additions & 5 deletions packages/server/lib/controllers/auth/postTableau.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const bodyValidation = z

const queryStringValidation = z
.object({
connection_id: connectionIdSchema,
connection_id: connectionIdSchema.optional(),
params: z.record(z.any()).optional(),
public_key: z.string().uuid(),
hmac: z.string().optional()
Expand Down Expand Up @@ -70,7 +70,7 @@ export const postPublicTableauAuthorization = asyncWrapper<PostPublicTableauAuth

const { account, environment } = res.locals;
const { pat_name: patName, pat_secret: patSecret, content_url: contentUrl }: PostPublicTableauAuthorization['Body'] = val.data;
const { connection_id: connectionId, params, hmac }: PostPublicTableauAuthorization['Querystring'] = queryStringVal.data;
const { connection_id: receivedConnectionId, params, hmac }: PostPublicTableauAuthorization['Querystring'] = queryStringVal.data;
const { providerConfigKey }: PostPublicTableauAuthorization['Params'] = paramVal.data;
const connectionConfig = params ? getConnectionConfig(params) : {};

Expand All @@ -91,7 +91,7 @@ export const postPublicTableauAuthorization = asyncWrapper<PostPublicTableauAuth
environment,
logCtx,
providerConfigKey,
connectionId,
connectionId: receivedConnectionId,
hmac,
res
});
Expand Down Expand Up @@ -138,6 +138,8 @@ export const postPublicTableauAuthorization = asyncWrapper<PostPublicTableauAuth

await logCtx.info('Tableau credentials creation was successful');
await logCtx.success();

const connectionId = receivedConnectionId || connectionService.generateConnectionId();
const [updatedConnection] = await connectionService.upsertTableauConnection({
connectionId,
providerConfigKey,
Expand Down Expand Up @@ -172,7 +174,7 @@ export const postPublicTableauAuthorization = asyncWrapper<PostPublicTableauAuth

void connectionCreationFailedHook(
{
connection: { connection_id: connectionId, provider_config_key: providerConfigKey },
connection: { connection_id: receivedConnectionId!, provider_config_key: providerConfigKey },
environment,
account,
auth_mode: 'TABLEAU',
Expand All @@ -196,7 +198,7 @@ export const postPublicTableauAuthorization = asyncWrapper<PostPublicTableauAuth
environmentId: environment.id,
metadata: {
providerConfigKey,
connectionId
connectionId: receivedConnectionId
}
});

Expand Down
8 changes: 5 additions & 3 deletions packages/server/lib/controllers/auth/postTba.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const bodyValidation = z

const queryStringValidation = z
.object({
connection_id: connectionIdSchema,
connection_id: connectionIdSchema.optional(),
params: z.record(z.any()).optional(),
hmac: z.string().optional(),
public_key: z.string().uuid()
Expand Down Expand Up @@ -64,7 +64,7 @@ export const postPublicTbaAuthorization = asyncWrapper<PostPublicTbaAuthorizatio

const { token_id: tokenId, token_secret: tokenSecret, oauth_client_id_override, oauth_client_secret_override } = body;

const { connection_id: connectionId, params, hmac }: PostPublicTbaAuthorization['Querystring'] = queryStringVal.data;
const { connection_id: receivedConnectionId, params, hmac }: PostPublicTbaAuthorization['Querystring'] = queryStringVal.data;
const { providerConfigKey }: PostPublicTbaAuthorization['Params'] = paramVal.data;

const logCtx = await logContextGetter.create(
Expand All @@ -77,7 +77,7 @@ export const postPublicTbaAuthorization = asyncWrapper<PostPublicTbaAuthorizatio
);
void analytics.track(AnalyticsTypes.PRE_TBA_AUTH, account.id);

await hmacCheck({ environment, logCtx, providerConfigKey, connectionId, hmac, res });
await hmacCheck({ environment, logCtx, providerConfigKey, connectionId: receivedConnectionId, hmac, res });

const config = await configService.getProviderConfig(providerConfigKey, environment.id);

Expand Down Expand Up @@ -139,6 +139,8 @@ export const postPublicTbaAuthorization = asyncWrapper<PostPublicTbaAuthorizatio
}
}

const connectionId = receivedConnectionId || connectionService.generateConnectionId();

const connectionResponse = await connectionTestHook(
config.provider,
provider,
Expand Down
Loading
Loading