Skip to content
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
77 changes: 75 additions & 2 deletions connection/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
ResultType,
} from "../query/query.ts";
import type { ConnectionParams } from "./connection_params.ts";
import * as scram from "./scram.ts";

export enum Format {
TEXT = 0,
Expand Down Expand Up @@ -363,9 +364,10 @@ export class Connection {
}
// scram-sha-256 password
case 10: {
throw new Error(
"Database server expected scram-sha-256 authentication, which is not supported at the moment",
await assertSuccessfulAuthentication(
await this.authenticateWithScramSha256(),
);
break;
}
default:
throw new Error(`Unknown auth message code ${code}`);
Expand Down Expand Up @@ -403,6 +405,77 @@ export class Connection {
return this.readMessage();
}

private async authenticateWithScramSha256(): Promise<Message> {
if (!this.connParams.password) {
throw new Error(
"Auth Error: attempting SCRAM-SHA-256 auth with password unset",
);
}

const client = new scram.Client(
this.connParams.user,
this.connParams.password,
);
const utf8 = new TextDecoder("utf-8");

// SASLInitialResponse
const clientFirstMessage = client.composeChallenge();
this.#packetWriter.clear();
this.#packetWriter.addCString("SCRAM-SHA-256");
this.#packetWriter.addInt32(clientFirstMessage.length);
this.#packetWriter.addString(clientFirstMessage);
this.#bufWriter.write(this.#packetWriter.flush(0x70));
this.#bufWriter.flush();

// AuthenticationSASLContinue
const saslContinue = await this.readMessage();
switch (saslContinue.type) {
case "R": {
if (saslContinue.reader.readInt32() != 11) {
throw new Error("AuthenticationSASLContinue is expected");
}
break;
}
case "E": {
throw parseError(saslContinue);
}
default: {
throw new Error("unexpected message");
}
}
const serverFirstMessage = utf8.decode(saslContinue.reader.readAllBytes());
client.receiveChallenge(serverFirstMessage);

// SASLResponse
const clientFinalMessage = client.composeResponse();
this.#packetWriter.clear();
this.#packetWriter.addString(clientFinalMessage);
this.#bufWriter.write(this.#packetWriter.flush(0x70));
this.#bufWriter.flush();

// AuthenticationSASLFinal
const saslFinal = await this.readMessage();
switch (saslFinal.type) {
case "R": {
if (saslFinal.reader.readInt32() !== 12) {
throw new Error("AuthenticationSASLFinal is expected");
}
break;
}
case "E": {
throw parseError(saslFinal);
}
default: {
throw new Error("unexpected message");
}
}
const serverFinalMessage = utf8.decode(saslFinal.reader.readAllBytes());
client.receiveResponse(serverFinalMessage);

// AuthenticationOK
return this.readMessage();
}

private _processBackendKeyData(msg: Message) {
this.#pid = msg.reader.readInt32();
this.#secretKey = msg.reader.readInt32();
Expand Down
6 changes: 6 additions & 0 deletions connection/packet_reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export class PacketReader {
return slice;
}

readAllBytes(): Uint8Array {
const slice = this.buffer.slice(this.offset);
this.offset = this.buffer.length;
return slice;
}

readString(length: number): string {
const bytes = this.readBytes(length);
return this.decoder.decode(bytes);
Expand Down
Loading