Skip to content

Commit

Permalink
Merge pull request #19 from supabase-community/feat/throw-errors-and-end
Browse files Browse the repository at this point in the history
feat: thrown backend errors are sent to the client
  • Loading branch information
gregnr authored Sep 13, 2024
2 parents 250ee0c + 4f7fc24 commit 13fb14d
Show file tree
Hide file tree
Showing 12 changed files with 395 additions and 220 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/pg-gateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"devDependencies": {
"@biomejs/biome": "^1.8.3",
"@electric-sql/pglite": "^0.2.6",
"@electric-sql/pglite": "^0.2.7",
"@nodeweb/knex": "^3.1.0-alpha.13",
"@nodeweb/pg": "^8.12.0-alpha.5",
"@std/bytes": "npm:@jsr/std__bytes@^1.0.2",
Expand Down
14 changes: 7 additions & 7 deletions packages/pg-gateway/src/auth/cert.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createBackendErrorMessage } from '../backend-error.js';
import { BackendError } from '../backend-error.js';
import type { BufferReader } from '../buffer-reader.js';
import type { BufferWriter } from '../buffer-writer.js';
import type { ConnectionState } from '../connection.types';
Expand Down Expand Up @@ -45,21 +45,21 @@ export class CertAuthFlow extends BaseAuthFlow {

async *handleClientMessage(message: BufferSource) {
if (!this.connectionState.tlsInfo) {
yield createBackendErrorMessage({
yield BackendError.create({
severity: 'FATAL',
code: '08000',
message: `ssl connection required when auth mode is 'certificate'`,
});
}).flush();
yield closeSignal;
return;
}

if (!this.connectionState.tlsInfo.clientCertificate) {
yield createBackendErrorMessage({
yield BackendError.create({
severity: 'FATAL',
code: '08000',
message: 'client certificate required',
});
}).flush();
yield closeSignal;
return;
}
Expand All @@ -73,11 +73,11 @@ export class CertAuthFlow extends BaseAuthFlow {
);

if (!isValid) {
yield createBackendErrorMessage({
yield BackendError.create({
severity: 'FATAL',
code: '08000',
message: 'client certificate is invalid',
});
}).flush();
yield closeSignal;
return;
}
Expand Down
8 changes: 4 additions & 4 deletions packages/pg-gateway/src/auth/md5.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { concat } from '@std/bytes/concat';
import { crypto } from '@std/crypto';
import { encodeHex } from '@std/encoding/hex';
import { createBackendErrorMessage } from '../backend-error.js';
import { BackendError } from '../backend-error.js';
import type { BufferReader } from '../buffer-reader.js';
import type { BufferWriter } from '../buffer-writer.js';
import type { ConnectionState } from '../connection.types';
import { BackendMessageCode } from '../message-codes';
import { BaseAuthFlow } from './base-auth-flow';
import { closeSignal } from '../signals.js';
import { BaseAuthFlow } from './base-auth-flow';

export type Md5AuthOptions = {
method: 'md5';
Expand Down Expand Up @@ -76,11 +76,11 @@ export class Md5AuthFlow extends BaseAuthFlow {
);

if (!isValid) {
yield createBackendErrorMessage({
yield BackendError.create({
severity: 'FATAL',
code: '28P01',
message: `password authentication failed for user "${this.username}"`,
});
}).flush();
yield closeSignal;
return;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/pg-gateway/src/auth/password.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createBackendErrorMessage } from '../backend-error.js';
import { BackendError } from '../backend-error.js';
import type { BufferReader } from '../buffer-reader.js';
import type { BufferWriter } from '../buffer-writer.js';
import type { ConnectionState } from '../connection.types';
Expand Down Expand Up @@ -72,11 +72,11 @@ export class PasswordAuthFlow extends BaseAuthFlow {
);

if (!isValid) {
yield createBackendErrorMessage({
yield BackendError.create({
severity: 'FATAL',
code: '28P01',
message: `password authentication failed for user "${this.username}"`,
});
}).flush();
yield closeSignal;
return;
}
Expand Down
12 changes: 6 additions & 6 deletions packages/pg-gateway/src/auth/sasl/scram-sha-256.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { decodeBase64, encodeBase64 } from '@std/encoding/base64';
import { createBackendErrorMessage } from '../../backend-error.js';
import { BackendError } from '../../backend-error.js';
import type { BufferReader } from '../../buffer-reader.js';
import type { BufferWriter } from '../../buffer-writer.js';
import type { ConnectionState } from '../../connection.types';
import { createHashKey, createHmacKey, pbkdf2, timingSafeEqual } from '../../crypto.js';
import { closeSignal } from '../../signals.js';
import type { AuthFlow } from '../base-auth-flow';
import { SaslMechanism } from './sasl-mechanism';
import { closeSignal } from '../../signals.js';

export type ScramSha256Data = {
salt: string;
Expand Down Expand Up @@ -163,11 +163,11 @@ export class ScramSha256AuthFlow extends SaslMechanism implements AuthFlow {
const saslMechanism = this.reader.cstring();

if (saslMechanism !== 'SCRAM-SHA-256') {
yield createBackendErrorMessage({
yield BackendError.create({
severity: 'FATAL',
code: '28000',
message: 'Unsupported SASL authentication mechanism',
});
}).flush();
yield closeSignal;
return;
}
Expand Down Expand Up @@ -207,11 +207,11 @@ export class ScramSha256AuthFlow extends SaslMechanism implements AuthFlow {
this.step = ScramSha256Step.Completed;
yield this.createAuthenticationSASLFinal(serverFinalMessage);
} catch (error) {
yield createBackendErrorMessage({
yield BackendError.create({
severity: 'FATAL',
code: '28000',
message: (error as Error).message,
});
}).flush();
yield closeSignal;
return;
}
Expand Down
205 changes: 120 additions & 85 deletions packages/pg-gateway/src/backend-error.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BufferWriter } from './buffer-writer.js';
import { BackendMessageCode } from './message-codes.js';

export interface BackendError {
interface BackendErrorParams {
severity: 'ERROR' | 'FATAL' | 'PANIC';
code: string;
message: string;
Expand All @@ -22,99 +22,134 @@ export interface BackendError {
}

/**
* Creates a backend error message
* Represents a backend error message
*
* @see https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-ERRORRESPONSE
*
* For error fields, @see https://www.postgresql.org/docs/current/protocol-error-fields.html#PROTOCOL-ERROR-FIELDS
*/
export function createBackendErrorMessage(error: BackendError) {
const writer = new BufferWriter();

writer.addString('S');
writer.addCString(error.severity);

writer.addString('V');
writer.addCString(error.severity);

writer.addString('C');
writer.addCString(error.code);

writer.addString('M');
writer.addCString(error.message);

if (error.detail !== undefined) {
writer.addString('D');
writer.addCString(error.detail);
}

if (error.hint !== undefined) {
writer.addString('H');
writer.addCString(error.hint);
}

if (error.position !== undefined) {
writer.addString('P');
writer.addCString(error.position);
}

if (error.internalPosition !== undefined) {
writer.addString('p');
writer.addCString(error.internalPosition);
}

if (error.internalQuery !== undefined) {
writer.addString('q');
writer.addCString(error.internalQuery);
}

if (error.where !== undefined) {
writer.addString('W');
writer.addCString(error.where);
}

if (error.schema !== undefined) {
writer.addString('s');
writer.addCString(error.schema);
}

if (error.table !== undefined) {
writer.addString('t');
writer.addCString(error.table);
}

if (error.column !== undefined) {
writer.addString('c');
writer.addCString(error.column);
}

if (error.dataType !== undefined) {
writer.addString('d');
writer.addCString(error.dataType);
}

if (error.constraint !== undefined) {
writer.addString('n');
writer.addCString(error.constraint);
}
export class BackendError {
severity!: 'ERROR' | 'FATAL' | 'PANIC';
code!: string;
message!: string;
detail?: string;
hint?: string;
position?: string;
internalPosition?: string;
internalQuery?: string;
where?: string;
schema?: string;
table?: string;
column?: string;
dataType?: string;
constraint?: string;
file?: string;
line?: string;
routine?: string;

if (error.file !== undefined) {
writer.addString('F');
writer.addCString(error.file);
constructor(params: BackendErrorParams) {
Object.assign(this, params);
}

if (error.line !== undefined) {
writer.addString('L');
writer.addCString(error.line);
/**
* Creates a backend error message
*
* @see https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-ERRORRESPONSE
*
* For error fields, @see https://www.postgresql.org/docs/current/protocol-error-fields.html#PROTOCOL-ERROR-FIELDS
*/
static create(params: BackendErrorParams) {
return new BackendError(params);
}

if (error.routine !== undefined) {
writer.addString('R');
writer.addCString(error.routine);
flush() {
const writer = new BufferWriter();

writer.addString('S');
writer.addCString(this.severity);

writer.addString('V');
writer.addCString(this.severity);

writer.addString('C');
writer.addCString(this.code);

writer.addString('M');
writer.addCString(this.message);

if (this.detail !== undefined) {
writer.addString('D');
writer.addCString(this.detail);
}

if (this.hint !== undefined) {
writer.addString('H');
writer.addCString(this.hint);
}

if (this.position !== undefined) {
writer.addString('P');
writer.addCString(this.position);
}

if (this.internalPosition !== undefined) {
writer.addString('p');
writer.addCString(this.internalPosition);
}

if (this.internalQuery !== undefined) {
writer.addString('q');
writer.addCString(this.internalQuery);
}

if (this.where !== undefined) {
writer.addString('W');
writer.addCString(this.where);
}

if (this.schema !== undefined) {
writer.addString('s');
writer.addCString(this.schema);
}

if (this.table !== undefined) {
writer.addString('t');
writer.addCString(this.table);
}

if (this.column !== undefined) {
writer.addString('c');
writer.addCString(this.column);
}

if (this.dataType !== undefined) {
writer.addString('d');
writer.addCString(this.dataType);
}

if (this.constraint !== undefined) {
writer.addString('n');
writer.addCString(this.constraint);
}

if (this.file !== undefined) {
writer.addString('F');
writer.addCString(this.file);
}

if (this.line !== undefined) {
writer.addString('L');
writer.addCString(this.line);
}

if (this.routine !== undefined) {
writer.addString('R');
writer.addCString(this.routine);
}

// Add null byte to the end
writer.addCString('');

return writer.flush(BackendMessageCode.ErrorMessage);
}

// Add null byte to the end
writer.addCString('');

return writer.flush(BackendMessageCode.ErrorMessage);
}
Loading

0 comments on commit 13fb14d

Please sign in to comment.