-
Notifications
You must be signed in to change notification settings - Fork 929
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
DataConnect + PGLite prototype. #7615
Changes from 17 commits
feb3560
412ae45
0d8ec81
766ebd2
0c99fce
5b4e0f6
36dd2be
b65fb1c
dab3523
3c8964c
0a4c9e3
13a8590
c86ce4e
890bcc2
63bcc6b
5e7da7c
a8d8aa6
3866a0e
d96874d
17601cc
dfd93f8
25c1bc0
2b7cdeb
78a4f47
feeb1b6
7a9d054
115224b
d959f67
f1e038e
1439370
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -68,20 +68,20 @@ | |
|
||
/** | ||
* Exports emulator data on clean exit (SIGINT or process end) | ||
* @param options | ||
Check warning on line 71 in src/emulator/controller.ts GitHub Actions / lint (20)
|
||
*/ | ||
export async function exportOnExit(options: any) { | ||
Check warning on line 73 in src/emulator/controller.ts GitHub Actions / lint (20)
|
||
const exportOnExitDir = options.exportOnExit; | ||
Check warning on line 74 in src/emulator/controller.ts GitHub Actions / lint (20)
|
||
if (exportOnExitDir) { | ||
try { | ||
utils.logBullet( | ||
`Automatically exporting data using ${FLAG_EXPORT_ON_EXIT_NAME} "${exportOnExitDir}" ` + | ||
"please wait for the export to finish...", | ||
); | ||
await exportEmulatorData(exportOnExitDir, options, /* initiatedBy= */ "exit"); | ||
} catch (e: any) { | ||
utils.logWarning(e); | ||
utils.logWarning(`Automatic export to "${exportOnExitDir}" failed, going to exit now...`); | ||
} | ||
} | ||
} | ||
|
@@ -851,6 +851,7 @@ | |
configDir, | ||
rc: options.rc, | ||
config: options.config, | ||
autostartPostgres: experiments.isEnabled("fdcpglite"), | ||
}); | ||
await startEmulator(dataConnectEmulator); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
The code in this directory is a very slightly modified version of https://github.com/supabase-community/pg-gateway/tree/next. | ||
Full credit for this code goes to @gregnr and the other contributors on that repo. | ||
|
||
Due to some known issues with how PGLite handles prepared statements, this versiom of pg-gateway includes middleware | ||
to remove the extra Ready for Query messages that break schema migration. Once these underlying issues with PGLite are fixed, | ||
we'll migrate to a normal dependency on pg-gateway. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import type { BufferReader } from '../buffer-reader'; | ||
import type { BufferWriter } from '../buffer-writer'; | ||
import type { ConnectionSignal } from '../connection'; | ||
import type { ConnectionState } from '../connection.types'; | ||
|
||
export interface AuthFlow { | ||
createInitialAuthMessage(): Uint8Array | undefined; | ||
handleClientMessage(message: BufferSource): AsyncGenerator<Uint8Array | ConnectionSignal>; | ||
isCompleted: boolean; | ||
} | ||
|
||
export abstract class BaseAuthFlow implements AuthFlow { | ||
protected reader: BufferReader; | ||
protected writer: BufferWriter; | ||
protected connectionState: ConnectionState; | ||
|
||
constructor(params: { | ||
reader: BufferReader; | ||
writer: BufferWriter; | ||
connectionState: ConnectionState; | ||
}) { | ||
this.reader = params.reader; | ||
this.writer = params.writer; | ||
this.connectionState = params.connectionState; | ||
} | ||
|
||
abstract createInitialAuthMessage(): Uint8Array | undefined; | ||
abstract handleClientMessage( | ||
message: BufferSource, | ||
): AsyncGenerator<Uint8Array | ConnectionSignal>; | ||
abstract get isCompleted(): boolean; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import type { PeerCertificate } from 'node:tls'; | ||
import { createBackendErrorMessage } from '../backend-error'; | ||
import type { BufferReader } from '../buffer-reader'; | ||
import type { BufferWriter } from '../buffer-writer'; | ||
import type { ConnectionState } from '../connection.types'; | ||
import { BaseAuthFlow } from './base-auth-flow'; | ||
import { closeSignal } from '../connection'; | ||
|
||
export type CertAuthOptions = { | ||
method: 'cert'; | ||
validateCredentials?: ( | ||
credentials: { | ||
username: string; | ||
certificate: PeerCertificate; | ||
}, | ||
connectionState: ConnectionState, | ||
) => boolean | Promise<boolean>; | ||
}; | ||
|
||
export class CertAuthFlow extends BaseAuthFlow { | ||
private auth: CertAuthOptions & { | ||
validateCredentials: NonNullable<CertAuthOptions['validateCredentials']>; | ||
}; | ||
private username: string; | ||
private completed = false; | ||
|
||
constructor(params: { | ||
auth: CertAuthOptions; | ||
username: string; | ||
reader: BufferReader; | ||
writer: BufferWriter; | ||
connectionState: ConnectionState; | ||
}) { | ||
super(params); | ||
this.auth = { | ||
...params.auth, | ||
validateCredentials: | ||
params.auth.validateCredentials ?? | ||
(async ({ username, certificate }) => { | ||
return certificate.subject.CN === username; | ||
}), | ||
}; | ||
this.username = params.username; | ||
} | ||
|
||
async *handleClientMessage(message: BufferSource) { | ||
// biome-ignore lint/correctness/noConstantCondition: TODO: detect TLS state | ||
if (false) { | ||
yield createBackendErrorMessage({ | ||
severity: 'FATAL', | ||
code: '08000', | ||
message: `ssl connection required when auth mode is 'certificate'`, | ||
}); | ||
yield closeSignal; | ||
return; | ||
} | ||
|
||
// biome-ignore lint/correctness/noConstantCondition: TODO: detect if cert authorized | ||
if (false) { | ||
yield createBackendErrorMessage({ | ||
severity: 'FATAL', | ||
code: '08000', | ||
message: 'client certificate is invalid', | ||
}); | ||
yield closeSignal; | ||
return; | ||
} | ||
|
||
// TODO: get peer cert and validate through hook | ||
const isValid = false; | ||
|
||
// const isValid = await this.auth.validateCredentials( | ||
// { | ||
// username: this.username, | ||
// certificate: this.socket.getPeerCertificate(), | ||
// }, | ||
// this.connectionState, | ||
// ); | ||
|
||
if (!isValid) { | ||
yield createBackendErrorMessage({ | ||
severity: 'FATAL', | ||
code: '08000', | ||
message: 'client certificate is invalid', | ||
}); | ||
yield closeSignal; | ||
return; | ||
} | ||
|
||
this.completed = true; | ||
} | ||
|
||
override createInitialAuthMessage() { | ||
return undefined; | ||
} | ||
|
||
get isCompleted(): boolean { | ||
return this.completed; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import type { BufferReader } from '../buffer-reader'; | ||
import type { BufferWriter } from '../buffer-writer'; | ||
import type { ConnectionState } from '../connection.types'; | ||
import type { AuthFlow } from './base-auth-flow'; | ||
import { CertAuthFlow, type CertAuthOptions } from './cert'; | ||
import { Md5AuthFlow, type Md5AuthOptions } from './md5'; | ||
import { PasswordAuthFlow, type PasswordAuthOptions } from './password'; | ||
import { ScramSha256AuthFlow, type ScramSha256AuthOptions } from './sasl/scram-sha-256'; | ||
import type { TrustAuthOptions } from './trust'; | ||
|
||
export type AuthOptions = | ||
| TrustAuthOptions | ||
| PasswordAuthOptions | ||
| Md5AuthOptions | ||
| ScramSha256AuthOptions | ||
| CertAuthOptions; | ||
|
||
export function createAuthFlow(options: { | ||
reader: BufferReader; | ||
writer: BufferWriter; | ||
auth: AuthOptions; | ||
username: string; | ||
connectionState: ConnectionState; | ||
}): AuthFlow { | ||
switch (options.auth.method) { | ||
case 'password': | ||
return new PasswordAuthFlow({ ...options, auth: options.auth }); | ||
case 'md5': | ||
return new Md5AuthFlow({ ...options, auth: options.auth }); | ||
case 'scram-sha-256': | ||
return new ScramSha256AuthFlow({ ...options, auth: options.auth }); | ||
case 'cert': | ||
return new CertAuthFlow({ ...options, auth: options.auth }); | ||
default: | ||
throw new Error(`Unsupported auth method: ${options.auth.method}`); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be handled? (not sure if this is directly copied from
pg-gateway
or not)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for us to actually authenticate the toolkit's creds - the FDC toolkit is trusted, so we are using 'trust' auth and will never hit this code.
I removed this while debugging some import issues that arose from converting these files to cjs.