Skip to content

Commit 1b3a193

Browse files
committed
feat: add Azure Auth
1 parent 77facfb commit 1b3a193

File tree

7 files changed

+39
-26
lines changed

7 files changed

+39
-26
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,14 @@ DBHub supports the following database connection string formats:
194194
| SQL Server | `sqlserver://[user]:[password]@[host]:[port]/[database]` | `sqlserver://user:password@localhost:1433/dbname` |
195195
| SQLite | `sqlite:///[path/to/file]` or `sqlite::memory:` | `sqlite:///path/to/database.db` or `sqlite::memory:` |
196196

197+
#### SQL Server
198+
199+
Extra query parameters:
200+
201+
#### authentication
202+
203+
- `authentication=azure-active-directory-access-token`. Only applicable when running from Azure. See [DefaultAzureCredential](https://learn.microsoft.com/en-us/azure/developer/javascript/sdk/authentication/credential-chains#use-defaultazurecredential-for-flexibility).
204+
197205
### Transport
198206

199207
- **stdio** (default) - for direct integration with tools like Claude Desktop:

src/connectors/interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export interface DSNParser {
4343
* - MySQL: "mysql://user:password@localhost:3306/dbname"
4444
* - SQLite: "sqlite:///path/to/database.db" or "sqlite::memory:"
4545
*/
46-
parse(dsn: string): any;
46+
parse(dsn: string): Promise<any>;
4747

4848
/**
4949
* Generate a sample DSN string for this connector type

src/connectors/mariadb/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Connector, ConnectorRegistry, DSNParser, QueryResult, TableColumn, Tabl
66
* Handles DSN strings like: mariadb://user:password@localhost:3306/dbname
77
*/
88
class MariadbDSNParser implements DSNParser {
9-
parse(dsn: string): mariadb.ConnectionConfig {
9+
async parse(dsn: string): Promise<mariadb.ConnectionConfig> {
1010
// Basic validation
1111
if (!this.isValidDSN(dsn)) {
1212
throw new Error(`Invalid MariaDB DSN: ${dsn}`);
@@ -63,7 +63,7 @@ export class MariaDBConnector implements Connector {
6363

6464
async connect(dsn: string): Promise<void> {
6565
try {
66-
const config = this.dsnParser.parse(dsn);
66+
const config = await this.dsnParser.parse(dsn);
6767

6868
this.pool = mariadb.createPool(config);
6969

src/connectors/mysql/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Connector, ConnectorRegistry, DSNParser, QueryResult, TableColumn, Tabl
66
* Handles DSN strings like: mysql://user:password@localhost:3306/dbname
77
*/
88
class MySQLDSNParser implements DSNParser {
9-
parse(dsn: string): mysql.ConnectionOptions {
9+
async parse(dsn: string): Promise<mysql.ConnectionOptions> {
1010
// Basic validation
1111
if (!this.isValidDSN(dsn)) {
1212
throw new Error(`Invalid MySQL DSN: ${dsn}`);
@@ -63,7 +63,7 @@ export class MySQLConnector implements Connector {
6363

6464
async connect(dsn: string): Promise<void> {
6565
try {
66-
const config = this.dsnParser.parse(dsn);
66+
const config = await this.dsnParser.parse(dsn);
6767
this.pool = mysql.createPool(config);
6868

6969
// Test the connection

src/connectors/postgres/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Connector, ConnectorRegistry, DSNParser, QueryResult, TableColumn, Tabl
77
* Handles DSN strings like: postgres://user:password@localhost:5432/dbname?sslmode=disable
88
*/
99
class PostgresDSNParser implements DSNParser {
10-
parse(dsn: string): pg.PoolConfig {
10+
async parse(dsn: string): Promise<pg.PoolConfig> {
1111
// Basic validation
1212
if (!this.isValidDSN(dsn)) {
1313
throw new Error(`Invalid PostgreSQL DSN: ${dsn}`);
@@ -66,7 +66,7 @@ export class PostgresConnector implements Connector {
6666

6767
async connect(dsn: string): Promise<void> {
6868
try {
69-
const config = this.dsnParser.parse(dsn);
69+
const config = await this.dsnParser.parse(dsn);
7070
this.pool = new Pool(config);
7171

7272
// Test the connection

src/connectors/sqlite/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import Database from 'better-sqlite3';
1818
* - sqlite::memory: (in-memory database)
1919
*/
2020
class SQLiteDSNParser implements DSNParser {
21-
parse(dsn: string): { dbPath: string } {
21+
async parse(dsn: string): Promise<{ dbPath: string }> {
2222
// Basic validation
2323
if (!this.isValidDSN(dsn)) {
2424
throw new Error(`Invalid SQLite DSN: ${dsn}`);
@@ -85,7 +85,7 @@ export class SQLiteConnector implements Connector {
8585
private dbPath: string = ':memory:'; // Default to in-memory database
8686

8787
async connect(dsn: string, initScript?: string): Promise<void> {
88-
const config = this.dsnParser.parse(dsn);
88+
const config = await this.dsnParser.parse(dsn);
8989
this.dbPath = config.dbPath;
9090

9191
try {

src/connectors/sqlserver/index.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {DefaultAzureCredential} from "@azure/identity";
77
* Expected format: mssql://username:password@host:port/database
88
*/
99
export class SQLServerDSNParser implements DSNParser {
10-
parse(dsn: string): sql.config {
10+
async parse(dsn: string): Promise<sql.config> {
1111
// Remove the protocol prefix
1212
if (!this.isValidDSN(dsn)) {
1313
throw new Error('Invalid SQL Server DSN format. Expected: sqlserver://username:password@host:port/database');
@@ -21,8 +21,6 @@ export class SQLServerDSNParser implements DSNParser {
2121
const user = url.username;
2222
const password = url.password ? decodeURIComponent(url.password) : '';
2323

24-
let authenticationType: string | undefined;
25-
2624
// Parse additional options from query parameters
2725
const options: Record<string, any> = {};
2826
for (const [key, value] of url.searchParams.entries()) {
@@ -54,19 +52,26 @@ export class SQLServerDSNParser implements DSNParser {
5452
},
5553
};
5654

57-
58-
const credential = new DefaultAzureCredential();
59-
const accessToken = credential.getToken('https://database.windows.net/.default');
60-
61-
62-
if (authenticationType) {
63-
const credential = new DefaultAzureCredential();
64-
config.authentication = {
65-
type: authenticationType,
66-
options: {
67-
token: credential.getToken('https://database.windows.net/.default'),
68-
},
69-
};
55+
// Handle Azure Active Directory authentication with access token
56+
if (options.authentication === "azure-active-directory-access-token") {
57+
try {
58+
// Create a credential instance
59+
const credential = new DefaultAzureCredential();
60+
61+
// Get token for SQL Server resource
62+
const token = await credential.getToken("https://database.windows.net/");
63+
64+
// Set the token in the config
65+
config.authentication = {
66+
type: "azure-active-directory-access-token",
67+
options: {
68+
token: token.token
69+
}
70+
};
71+
} catch (error: unknown) {
72+
const errorMessage = error instanceof Error ? error.message : String(error);
73+
throw new Error(`Failed to get Azure AD token: ${errorMessage}`);
74+
}
7075
}
7176

7277
return config;
@@ -99,7 +104,7 @@ export class SQLServerConnector implements Connector {
99104

100105
async connect(dsn: string): Promise<void> {
101106
try {
102-
this.config = this.dsnParser.parse(dsn);
107+
this.config = await this.dsnParser.parse(dsn);
103108

104109
if (!this.config.options) {
105110
this.config.options = {};

0 commit comments

Comments
 (0)