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

Pass through user headers from reverse proxy for commit author #168

Merged
merged 6 commits into from
Apr 23, 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
40 changes: 40 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,46 @@ Or you can run a Dolt SQL server for an existing database on your local machine:

When you enter your database configuration from the UI, you can use `my-doltdb` as the host name to connect to the database running in that container.

## Using a reverse proxy

In some circumstances you may want to add a reverse proxy in front of the Dolt Workbench
for authentication or other purposes. If you'd like to use the authenticated user from the
proxy as the author for commits or tags, you can pass through the user headers.

For example, given this [NGINX](https://www.nginx.com/) configuration that implements basic authentication:

```conf
events {}

http {
server {
listen 80;
server_name localhost;

location / {
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;

proxy_pass http://workbench:3000;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-User $remote_user;
proxy_set_header X-Forwarded-Email [email protected];
}
}
}
```

The `X-Forwarded-User` and `X-Forwarded-Email` headers are passed through to the workbench
and can used as the
[author](https://docs.dolthub.com/sql-reference/version-control/dolt-sql-procedures#options-6)
when creating a commit. Simply check the "Use name and email from headers as commit
author" checkbox. The commit will be created with the user and email.

You can also utilize this checkbox when creating releases and merging pull requests. If
the headers are not properly configured the checkbox will be disabled.

## Contact

You can reach us on [Discord](https://discord.com/invite/RFwfYpu) or [file a GitHub issue](https://github.com/dolthub/dolt-workbench/issues).
1 change: 1 addition & 0 deletions docker/examples/nginx-reverse-proxy/.htpasswd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
taylor:$apr1$0jAEdtKz$bijebRrD5MD9MR0buo6eH1
12 changes: 12 additions & 0 deletions docker/examples/nginx-reverse-proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# nginx-reverse-proxy

Adds a reverse proxy in front of the Dolt Workbench that implements basic authentication.
It passes through user headers that can be used by the workbench as the author of commits
and tags.

## Getting started

```
% docker compose build
% docker compose up
```
19 changes: 19 additions & 0 deletions docker/examples/nginx-reverse-proxy/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
services:
combined:
build:
context: ../../../
dockerfile: docker/Dockerfile
ports:
- 3000:3000
- 9002:9002
image: docker.io/dolthub/dolt-workbench:latest

proxy:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./.htpasswd:/etc/nginx/.htpasswd:ro
depends_on:
- combined
20 changes: 20 additions & 0 deletions docker/examples/nginx-reverse-proxy/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
events {}

http {
server {
listen 80;
server_name localhost;

location / {
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;

proxy_pass http://combined:3000;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-User $remote_user;
proxy_set_header X-Forwarded-Email [email protected];
}
}
}
9 changes: 7 additions & 2 deletions graphql-server/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ type Mutation {
createSchema(schemaName: String!): Boolean!
resetDatabase: Boolean!
loadDataFile(tableName: String!, refName: String!, databaseName: String!, importOp: ImportOperation!, fileType: FileType!, file: Upload!, modifier: LoadDataModifier): Boolean!
mergePull(databaseName: String!, fromBranchName: String!, toBranchName: String!): Boolean!
createTag(tagName: String!, databaseName: String!, message: String, fromRefName: String!): String!
mergePull(fromBranchName: String!, toBranchName: String!, databaseName: String!, author: AuthorInfo): Boolean!
createTag(tagName: String!, databaseName: String!, message: String, fromRefName: String!, author: AuthorInfo): String!
deleteTag(databaseName: String!, tagName: String!): Boolean!
}

Expand All @@ -366,4 +366,9 @@ scalar Upload
enum LoadDataModifier {
Ignore
Replace
}

input AuthorInfo {
name: String!
email: String!
}
3 changes: 2 additions & 1 deletion graphql-server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { GraphQLModule } from "@nestjs/graphql";
import { TerminusModule } from "@nestjs/terminus";
import { ConnectionProvider } from "./connections/connection.provider";
import { DataStoreModule } from "./dataStore/dataStore.module";
import { FileStoreModule } from "./fileStore/fileStore.module";
import resolvers from "./resolvers";
Expand All @@ -19,6 +20,6 @@ import resolvers from "./resolvers";
ConfigModule.forRoot({ isGlobal: true }),
DataStoreModule,
],
providers: resolvers,
providers: [ConnectionProvider, ...resolvers],
})
export class AppModule {}
4 changes: 2 additions & 2 deletions graphql-server/src/branches/branch.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
ResolveField,
Resolver,
} from "@nestjs/graphql";
import { ConnectionResolver } from "../connections/connection.resolver";
import { ConnectionProvider } from "../connections/connection.provider";
import { RawRow } from "../queryFactory/types";
import { Table } from "../tables/table.model";
import { TableResolver } from "../tables/table.resolver";
Expand Down Expand Up @@ -53,7 +53,7 @@ class ListBranchesArgs extends DBArgsWithOffset {
@Resolver(_of => Branch)
export class BranchResolver {
constructor(
private readonly conn: ConnectionResolver,
private readonly conn: ConnectionProvider,
private readonly tableResolver: TableResolver,
) {}

Expand Down
4 changes: 2 additions & 2 deletions graphql-server/src/commits/commit.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql";
import { ConnectionResolver } from "../connections/connection.resolver";
import { ConnectionProvider } from "../connections/connection.provider";
import { RawRow } from "../queryFactory/types";
import { ROW_LIMIT, getNextOffset } from "../utils";
import { DBArgsWithOffset } from "../utils/commonTypes";
Expand All @@ -23,7 +23,7 @@ export class ListCommitsArgs extends DBArgsWithOffset {

@Resolver(_of => Commit)
export class CommitResolver {
constructor(private readonly conn: ConnectionResolver) {}
constructor(private readonly conn: ConnectionProvider) {}

@Query(_returns => CommitList)
async commits(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Resolver } from "@nestjs/graphql";
import { Injectable } from "@nestjs/common";
import * as mysql from "mysql2/promise";
import { DataSource } from "typeorm";
import { DatabaseType } from "../databases/database.enum";
Expand All @@ -19,8 +19,8 @@ export class WorkbenchConfig {
schema?: string; // Postgres only
}

@Resolver()
export class ConnectionResolver {
@Injectable()
export class ConnectionProvider {
private ds: DataSource | undefined;

private qf: QueryFactory | undefined;
Expand Down
4 changes: 2 additions & 2 deletions graphql-server/src/databases/database.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Query,
Resolver,
} from "@nestjs/graphql";
import { ConnectionResolver } from "../connections/connection.resolver";
import { ConnectionProvider } from "../connections/connection.provider";
import { DataStoreService } from "../dataStore/dataStore.service";
import { FileStoreService } from "../fileStore/fileStore.service";
import { DBArgs, SchemaArgs } from "../utils/commonTypes";
Expand Down Expand Up @@ -66,7 +66,7 @@ class RemoveDatabaseConnectionArgs {
@Resolver(_of => DatabaseConnection)
export class DatabaseResolver {
constructor(
private readonly conn: ConnectionResolver,
private readonly conn: ConnectionProvider,
private readonly fileStoreService: FileStoreService,
private readonly dataStoreService: DataStoreService,
) {}
Expand Down
4 changes: 2 additions & 2 deletions graphql-server/src/diffStats/diffStat.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql";
import { ConnectionResolver } from "../connections/connection.resolver";
import { ConnectionProvider } from "../connections/connection.provider";
import { CommitDiffType } from "../diffSummaries/diffSummary.enums";
import { DBArgs } from "../utils/commonTypes";
import { DiffStat, fromDoltDiffStat } from "./diffStat.model";
Expand All @@ -24,7 +24,7 @@ export class DiffStatArgs extends DBArgs {

@Resolver(_of => DiffStat)
export class DiffStatResolver {
constructor(private readonly conn: ConnectionResolver) {}
constructor(private readonly conn: ConnectionProvider) {}

@Query(_returns => DiffStat)
async diffStat(@Args() args: DiffStatArgs): Promise<DiffStat> {
Expand Down
4 changes: 2 additions & 2 deletions graphql-server/src/diffSummaries/diffSummary.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql";
import { ConnectionResolver } from "../connections/connection.resolver";
import { ConnectionProvider } from "../connections/connection.provider";
import { checkArgs } from "../diffStats/diffStat.resolver";
import { QueryFactory } from "../queryFactory";
import { DBArgs } from "../utils/commonTypes";
Expand All @@ -26,7 +26,7 @@ class DiffSummaryArgs extends DBArgs {

@Resolver(_of => DiffSummary)
export class DiffSummaryResolver {
constructor(private readonly conn: ConnectionResolver) {}
constructor(private readonly conn: ConnectionProvider) {}

@Query(_returns => [DiffSummary])
async diffSummaries(@Args() args: DiffSummaryArgs): Promise<DiffSummary[]> {
Expand Down
4 changes: 2 additions & 2 deletions graphql-server/src/docs/doc.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Args, ArgsType, Field, Query, Resolver } from "@nestjs/graphql";
import { ConnectionResolver } from "../connections/connection.resolver";
import { ConnectionProvider } from "../connections/connection.provider";
import { RefArgs } from "../utils/commonTypes";
import { DocType } from "./doc.enum";
import { Doc, DocList, fromDoltDocsRow } from "./doc.model";
Expand All @@ -12,7 +12,7 @@ class GetDefaultDocArgs extends RefArgs {

@Resolver(_of => Doc)
export class DocsResolver {
constructor(private readonly conn: ConnectionResolver) {}
constructor(private readonly conn: ConnectionProvider) {}

@Query(_returns => DocList)
async docs(
Expand Down
15 changes: 11 additions & 4 deletions graphql-server/src/pulls/pull.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
Resolver,
} from "@nestjs/graphql";
import { CommitResolver } from "../commits/commit.resolver";
import { ConnectionResolver } from "../connections/connection.resolver";
import { DBArgs } from "../utils/commonTypes";
import { ConnectionProvider } from "../connections/connection.provider";
import { AuthorInfo, DBArgs } from "../utils/commonTypes";
import { PullWithDetails, fromAPIModelPullWithDetails } from "./pull.model";

@ArgsType()
Expand All @@ -20,10 +20,16 @@ class PullArgs extends DBArgs {
toBranchName: string;
}

@ArgsType()
class MergePullArgs extends PullArgs {
@Field({ nullable: true })
author?: AuthorInfo;
}

@Resolver(_of => PullWithDetails)
export class PullResolver {
constructor(
private readonly conn: ConnectionResolver,
private readonly conn: ConnectionProvider,
private readonly commitResolver: CommitResolver,
) {}

Expand All @@ -41,12 +47,13 @@ export class PullResolver {
}

@Mutation(_returns => Boolean)
async mergePull(@Args() args: PullArgs): Promise<boolean> {
async mergePull(@Args() args: MergePullArgs): Promise<boolean> {
const conn = this.conn.connection();
await conn.callMerge({
databaseName: args.databaseName,
fromBranchName: args.fromBranchName,
toBranchName: args.toBranchName,
author: args.author,
});
return true;
}
Expand Down
31 changes: 20 additions & 11 deletions graphql-server/src/queryFactory/dolt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import * as myqh from "../mysql/queries";
import { mapTablesRes } from "../mysql/utils";
import * as t from "../types";
import * as qh from "./queries";
import { handleRefNotFound, unionCols } from "./utils";
import { getAuthorString, handleRefNotFound, unionCols } from "./utils";

export class DoltQueryFactory
extends MySQLQueryFactory
Expand Down Expand Up @@ -345,11 +345,19 @@ export class DoltQueryFactory
args: t.TagArgs & {
fromRefName: string;
message?: string;
author?: t.CommitAuthor;
},
): t.PR {
const params = [args.tagName, args.fromRefName];
if (args.message) {
params.push(args.message);
}
if (args.author) {
params.push(getAuthorString(args.author));
}
return this.query(
qh.getCallNewTag(!!args.message),
[args.tagName, args.fromRefName, args.message],
qh.getCallNewTag(!!args.message, !!args.author),
params,
args.databaseName,
);
}
Expand All @@ -358,20 +366,21 @@ export class DoltQueryFactory
return this.query(qh.callDeleteTag, [args.tagName], args.databaseName);
}

async callMerge(args: t.BranchesArgs): Promise<boolean> {
async callMerge(
args: t.BranchesArgs & { author?: t.CommitAuthor },
): Promise<boolean> {
return this.queryMultiple(
async query => {
await query("BEGIN");

const res = await query(qh.callMerge, [
const params = [
args.fromBranchName,
`Merge branch ${args.fromBranchName}`,
// TODO: add commit author
// commitAuthor: {
// name: currentUser.username,
// email: currentUser.emailAddressesList[0].address,
// },
]);
];
if (args.author) {
params.push(getAuthorString(args.author));
}
const res = await query(qh.getCallMerge(!!args.author), params);

if (res.length && res[0].conflicts !== "0") {
await query("ROLLBACK");
Expand Down
3 changes: 2 additions & 1 deletion graphql-server/src/queryFactory/dolt/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ export const threeDotSchemaDiffQuery = `SELECT * FROM DOLT_SCHEMA_DIFF(?, ?)`;

// PULLS

export const callMerge = `CALL DOLT_MERGE(?, "--no-ff", "-m", ?)`;
export const getCallMerge = (hasAuthor = false) =>
`CALL DOLT_MERGE(?, "--no-ff", "-m", ?${getAuthorNameString(hasAuthor)})`;

// TAGS

Expand Down
6 changes: 5 additions & 1 deletion graphql-server/src/queryFactory/dolt/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RawRows } from "../types";
import { CommitAuthor, RawRows } from "../types";

export async function handleRefNotFound<T>(q: () => Promise<T>): Promise<T> {
try {
Expand All @@ -24,3 +24,7 @@ export function unionCols(a: RawRows, b: RawRows): RawRows {
}, set);
return unionArray;
}

export function getAuthorString(commitAuthor: CommitAuthor): string {
return `${commitAuthor.name} <${commitAuthor.email}>`;
}
Loading
Loading