Skip to content

Commit 11c0833

Browse files
authored
Merge pull request #1 from jgoux/feat/sasl
feat(auth): SASL SCRAM-SHA-256 support
2 parents b5e542d + 82a1310 commit 11c0833

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2017
-887
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ dist/
33
dbs/
44
*.pem
55
*.srl
6+
tsconfig.tsbuildinfo

biome.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
3+
"organizeImports": {
4+
"enabled": true
5+
},
6+
"linter": {
7+
"enabled": true,
8+
"rules": {
9+
"recommended": true
10+
}
11+
},
12+
"formatter": {
13+
"enabled": true,
14+
"indentStyle": "space",
15+
"indentWidth": 2
16+
},
17+
"javascript": {
18+
"formatter": {
19+
"quoteStyle": "single"
20+
}
21+
}
22+
}

examples/pglite-auth/biome.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
3+
"extends": ["../../biome.json"]
4+
}

examples/pglite/index.ts renamed to examples/pglite-auth/cert.ts

+16-17
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
1-
import { PGlite } from '@electric-sql/pglite';
1+
import fs from 'node:fs';
22
import net from 'node:net';
3-
import { PostgresConnection, hashMd5Password } from 'pg-gateway';
3+
import { PGlite } from '@electric-sql/pglite';
4+
import { type BackendError, PostgresConnection } from 'pg-gateway';
45

56
const db = new PGlite();
67

78
const server = net.createServer((socket) => {
89
const connection = new PostgresConnection(socket, {
910
serverVersion: '16.3 (PGlite 0.2.0)',
10-
authMode: 'md5Password',
11-
async validateCredentials(credentials) {
12-
if (credentials.authMode === 'md5Password') {
13-
const { hash, salt } = credentials;
14-
const expectedHash = await hashMd5Password(
15-
'postgres',
16-
'postgres',
17-
salt
18-
);
19-
return hash === expectedHash;
20-
}
21-
return false;
11+
tls: {
12+
key: fs.readFileSync('key.pem'),
13+
cert: fs.readFileSync('cert.pem'),
14+
},
15+
auth: {
16+
method: 'cert',
2217
},
18+
2319
async onStartup() {
2420
// Wait for PGlite to be ready before further processing
2521
await db.waitReady;
@@ -33,10 +29,13 @@ const server = net.createServer((socket) => {
3329

3430
// Forward raw message to PGlite
3531
try {
36-
const [[_, responseData]] = await db.execProtocol(data);
37-
connection.sendData(responseData);
32+
const [result] = await db.execProtocol(data);
33+
if (result) {
34+
const [_, responseData] = result;
35+
connection.sendData(responseData);
36+
}
3837
} catch (err) {
39-
connection.sendError(err);
38+
connection.sendError(err as BackendError);
4039
connection.sendReadyForQuery();
4140
}
4241
return true;

examples/pglite-auth/md5.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import net from 'node:net';
2+
import { PGlite } from '@electric-sql/pglite';
3+
import {
4+
type BackendError,
5+
PostgresConnection,
6+
createPreHashedPassword,
7+
} from 'pg-gateway';
8+
9+
const db = new PGlite();
10+
11+
const server = net.createServer((socket) => {
12+
const connection = new PostgresConnection(socket, {
13+
serverVersion: '16.3 (PGlite 0.2.0)',
14+
auth: {
15+
method: 'md5',
16+
getPreHashedPassword({ username }) {
17+
return createPreHashedPassword(username, 'postgres');
18+
},
19+
},
20+
21+
async onStartup() {
22+
// Wait for PGlite to be ready before further processing
23+
await db.waitReady;
24+
return false;
25+
},
26+
async onMessage(data, { isAuthenticated }) {
27+
// Only forward messages to PGlite after authentication
28+
if (!isAuthenticated) {
29+
return false;
30+
}
31+
32+
// Forward raw message to PGlite
33+
try {
34+
const [result] = await db.execProtocol(data);
35+
if (result) {
36+
const [_, responseData] = result;
37+
connection.sendData(responseData);
38+
}
39+
} catch (err) {
40+
connection.sendError(err as BackendError);
41+
connection.sendReadyForQuery();
42+
}
43+
return true;
44+
},
45+
});
46+
47+
socket.on('end', () => {
48+
console.log('Client disconnected');
49+
});
50+
});
51+
52+
server.listen(5432, () => {
53+
console.log('Server listening on port 5432');
54+
});

examples/pglite-auth/package.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "pglite-auth-example",
3+
"type": "module",
4+
"scripts": {
5+
"format": "biome format --write .",
6+
"lint": "biome lint --error-on-warnings .",
7+
"type-check": "tsc --noEmit"
8+
},
9+
"dependencies": {
10+
"pg-gateway": "*"
11+
},
12+
"devDependencies": {
13+
"@biomejs/biome": "1.8.3",
14+
"@electric-sql/pglite": "0.2.0-alpha.7",
15+
"@total-typescript/tsconfig": "^1.0.4",
16+
"@types/node": "^20.14.11",
17+
"tsx": "^4.16.2",
18+
"typescript": "^5.5.3"
19+
}
20+
}

examples/pglite-auth/password.ts

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import net from 'node:net';
2+
import { PGlite } from '@electric-sql/pglite';
3+
import { type BackendError, PostgresConnection } from 'pg-gateway';
4+
5+
const db = new PGlite();
6+
7+
const server = net.createServer((socket) => {
8+
const connection = new PostgresConnection(socket, {
9+
serverVersion: '16.3 (PGlite 0.2.0)',
10+
auth: {
11+
method: 'password',
12+
// this is the password stored in the server
13+
getClearTextPassword(credentials) {
14+
return 'postgres';
15+
},
16+
// uncomment to override the default password validation logic
17+
// async validateCredentials(credentials) {
18+
// const { clearTextPassword, password } = credentials;
19+
// // we allow case insensitive password validation
20+
// return password.toUpperCase() === clearTextPassword.toUpperCase();
21+
// },
22+
},
23+
24+
async onStartup() {
25+
// Wait for PGlite to be ready before further processing
26+
await db.waitReady;
27+
return false;
28+
},
29+
async onMessage(data, { isAuthenticated }) {
30+
// Only forward messages to PGlite after authentication
31+
if (!isAuthenticated) {
32+
return false;
33+
}
34+
35+
// Forward raw message to PGlite
36+
// Forward raw message to PGlite
37+
try {
38+
const [result] = await db.execProtocol(data);
39+
if (result) {
40+
const [_, responseData] = result;
41+
connection.sendData(responseData);
42+
}
43+
} catch (err) {
44+
connection.sendError(err as BackendError);
45+
connection.sendReadyForQuery();
46+
}
47+
return true;
48+
},
49+
});
50+
51+
socket.on('end', () => {
52+
console.log('Client disconnected');
53+
});
54+
});
55+
56+
server.listen(5432, () => {
57+
console.log('Server listening on port 5432');
58+
});

examples/pglite-auth/scram-sha-256.ts

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import net from 'node:net';
2+
import { PGlite } from '@electric-sql/pglite';
3+
import {
4+
type BackendError,
5+
PostgresConnection,
6+
createScramSha256Data,
7+
} from 'pg-gateway';
8+
9+
const db = new PGlite();
10+
11+
const server = net.createServer((socket) => {
12+
const connection = new PostgresConnection(socket, {
13+
serverVersion: '16.3 (PGlite 0.2.0)',
14+
auth: {
15+
method: 'scram-sha-256',
16+
async getScramSha256Data(credentials) {
17+
// Utility function to generate scram-sha-256 data (like salt) for the given user
18+
// You would likely store this info in a database and retrieve it here
19+
return createScramSha256Data('postgres');
20+
},
21+
},
22+
async onStartup() {
23+
// Wait for PGlite to be ready before further processing
24+
await db.waitReady;
25+
return false;
26+
},
27+
async onMessage(data, { isAuthenticated }) {
28+
// Only forward messages to PGlite after authentication
29+
if (!isAuthenticated) {
30+
return false;
31+
}
32+
33+
// Forward raw message to PGlite
34+
try {
35+
const [result] = await db.execProtocol(data);
36+
if (result) {
37+
const [_, responseData] = result;
38+
connection.sendData(responseData);
39+
}
40+
} catch (err) {
41+
connection.sendError(err as BackendError);
42+
connection.sendReadyForQuery();
43+
}
44+
return true;
45+
},
46+
});
47+
48+
socket.on('close', () => {
49+
console.log('Client disconnected');
50+
});
51+
});
52+
53+
server.listen(5432, () => {
54+
console.log('Server listening on port 5432');
55+
});

examples/pglite-auth/trust.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import net from 'node:net';
2+
import { PGlite } from '@electric-sql/pglite';
3+
import { type BackendError, PostgresConnection } from 'pg-gateway';
4+
5+
const db = new PGlite();
6+
7+
const server = net.createServer((socket) => {
8+
const connection = new PostgresConnection(socket, {
9+
serverVersion: '16.3 (PGlite 0.2.0)',
10+
auth: {
11+
method: 'trust',
12+
},
13+
14+
async onStartup() {
15+
// Wait for PGlite to be ready before further processing
16+
await db.waitReady;
17+
return false;
18+
},
19+
async onMessage(data, { isAuthenticated }) {
20+
// Only forward messages to PGlite after authentication
21+
if (!isAuthenticated) {
22+
return false;
23+
}
24+
25+
// Forward raw message to PGlite
26+
try {
27+
const [result] = await db.execProtocol(data);
28+
if (result) {
29+
const [_, responseData] = result;
30+
connection.sendData(responseData);
31+
}
32+
} catch (err) {
33+
connection.sendError(err as BackendError);
34+
connection.sendReadyForQuery();
35+
}
36+
return true;
37+
},
38+
});
39+
40+
socket.on('end', () => {
41+
console.log('Client disconnected');
42+
});
43+
});
44+
45+
server.listen(5432, () => {
46+
console.log('Server listening on port 5432');
47+
});

examples/pglite-auth/tsconfig.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "@total-typescript/tsconfig/tsc/no-dom/app",
3+
"include": ["*.ts"]
4+
}

examples/pglite-multiple/biome.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
3+
"extends": ["../../biome.json"]
4+
}

0 commit comments

Comments
 (0)