Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add passkey checker on startup. (#1720)
* Ensure the passkey can decrypt all users on startup.

* Add passkey checker

* Add support for NEDB too

* Return

* Fix based on review feedback

* a space
  • Loading branch information
Half-Shot committed Jun 2, 2023
1 parent 767e892 commit d49f41d
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 6 deletions.
1 change: 1 addition & 0 deletions changelog.d/1720.bugfix
@@ -0,0 +1 @@
Ensure that all passwords can be decrypted on startup, to detect any issues with the provided passkey.
2 changes: 2 additions & 0 deletions src/bridge/IrcBridge.ts
Expand Up @@ -650,6 +650,8 @@ export class IrcBridge {
throw Error("Incorrect database config");
}

await this.dataStore.ensurePasskeyCanDecrypt();

await this.dataStore.removeConfigMappings();

if (this.activityTracker) {
Expand Down
2 changes: 2 additions & 0 deletions src/datastore/DataStore.ts
Expand Up @@ -159,6 +159,8 @@ export interface DataStore extends ProvisioningStore {

storeIrcClientConfig(config: IrcClientConfig): Promise<void>;

ensurePasskeyCanDecrypt(): Promise<void>;

getMatrixUserByLocalpart(localpart: string): Promise<MatrixUser|null>;

getUserFeatures(userId: string): Promise<UserFeatures>;
Expand Down
36 changes: 31 additions & 5 deletions src/datastore/NedbDataStore.ts
Expand Up @@ -569,12 +569,15 @@ export class NeDBDataStore implements DataStore {
}
const clientConfig = new IrcClientConfig(userId, domain, configData);
const encryptedPass = clientConfig.getPassword();
if (encryptedPass) {
if (!this.cryptoStore) {
throw new Error(`Cannot decrypt password of ${userId} - no private key`);
if (encryptedPass && this.cryptoStore) {
// NOT fatal, but really worrying.
try {
const decryptedPass = this.cryptoStore.decrypt(encryptedPass);
clientConfig.setPassword(decryptedPass);
}
catch (ex) {
log.warn(`Failed to decrypt password for ${userId} ${domain}`, ex);
}
const decryptedPass = this.cryptoStore.decrypt(encryptedPass);
clientConfig.setPassword(decryptedPass);
}
return clientConfig;
}
Expand Down Expand Up @@ -610,6 +613,29 @@ export class NeDBDataStore implements DataStore {
await this.userStore.setMatrixUser(user);
}

public async ensurePasskeyCanDecrypt(): Promise<void> {
if (!this.cryptoStore) {
return;
}
const docs = await this.userStore.select<unknown, {id: string; data: { client_config: ClientConfigMap }}>({
type: "matrix",
"data.client_config": {$exists: true},
});
for (const { id: userId, data } of docs) {
for (const [domain, clientConfig] of Object.entries(data.client_config)) {
if (clientConfig.password) {
try {
this.cryptoStore.decrypt(clientConfig.password);
}
catch (ex) {
log.error(`Failed to decrypt password for ${userId} on ${domain}`, ex);
throw Error('Cannot decrypt user password, refusing to continue', { cause: ex });
}
}
}
}
}

public async getUserFeatures(userId: string): Promise<UserFeatures> {
const matrixUser = await this.userStore.getMatrixUser(userId);
return matrixUser ? (matrixUser.get("features") as UserFeatures || {}) : {};
Expand Down
26 changes: 25 additions & 1 deletion src/datastore/postgres/PgDataStore.ts
Expand Up @@ -516,7 +516,13 @@ export class PgDataStore implements DataStore, ProvisioningStore {
const row = res.rows[0];
const config = row.config || {}; // This may not be defined.
if (row.password && this.cryptoStore) {
config.password = this.cryptoStore.decrypt(row.password);
// NOT fatal, but really worrying.
try {
config.password = this.cryptoStore.decrypt(row.password);
}
catch (ex) {
log.warn(`Failed to decrypt password for ${userId} ${domain}`, ex);
}
}
return new IrcClientConfig(userId, domain, config);
}
Expand Down Expand Up @@ -545,6 +551,24 @@ export class PgDataStore implements DataStore, ProvisioningStore {
await this.pgPool.query(statement, Object.values(parameters));
}


public async ensurePasskeyCanDecrypt(): Promise<void> {
if (!this.cryptoStore) {
return;
}
const res = await this.pgPool.query<{password: string, user_id: string, domain: string}>(
"SELECT password, user_id, domain FROM client_config WHERE password IS NOT NULL");
for (const { password, user_id, domain } of res.rows) {
try {
this.cryptoStore.decrypt(password);
}
catch (ex) {
log.error(`Failed to decrypt password for ${user_id} on ${domain}`, ex);
throw Error('Cannot decrypt user password, refusing to continue', { cause: ex });
}
}
}

public async getMatrixUserByLocalpart(localpart: string): Promise<MatrixUser|null> {
const res = await this.pgPool.query("SELECT user_id, data FROM matrix_users WHERE user_id = $1", [
`@${localpart}:${this.bridgeDomain}`,
Expand Down

0 comments on commit d49f41d

Please sign in to comment.