Skip to content

Commit d49f41d

Browse files
authored
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
1 parent 767e892 commit d49f41d

File tree

5 files changed

+61
-6
lines changed

5 files changed

+61
-6
lines changed

changelog.d/1720.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure that all passwords can be decrypted on startup, to detect any issues with the provided passkey.

src/bridge/IrcBridge.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,8 @@ export class IrcBridge {
650650
throw Error("Incorrect database config");
651651
}
652652

653+
await this.dataStore.ensurePasskeyCanDecrypt();
654+
653655
await this.dataStore.removeConfigMappings();
654656

655657
if (this.activityTracker) {

src/datastore/DataStore.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ export interface DataStore extends ProvisioningStore {
159159

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

162+
ensurePasskeyCanDecrypt(): Promise<void>;
163+
162164
getMatrixUserByLocalpart(localpart: string): Promise<MatrixUser|null>;
163165

164166
getUserFeatures(userId: string): Promise<UserFeatures>;

src/datastore/NedbDataStore.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -569,12 +569,15 @@ export class NeDBDataStore implements DataStore {
569569
}
570570
const clientConfig = new IrcClientConfig(userId, domain, configData);
571571
const encryptedPass = clientConfig.getPassword();
572-
if (encryptedPass) {
573-
if (!this.cryptoStore) {
574-
throw new Error(`Cannot decrypt password of ${userId} - no private key`);
572+
if (encryptedPass && this.cryptoStore) {
573+
// NOT fatal, but really worrying.
574+
try {
575+
const decryptedPass = this.cryptoStore.decrypt(encryptedPass);
576+
clientConfig.setPassword(decryptedPass);
577+
}
578+
catch (ex) {
579+
log.warn(`Failed to decrypt password for ${userId} ${domain}`, ex);
575580
}
576-
const decryptedPass = this.cryptoStore.decrypt(encryptedPass);
577-
clientConfig.setPassword(decryptedPass);
578581
}
579582
return clientConfig;
580583
}
@@ -610,6 +613,29 @@ export class NeDBDataStore implements DataStore {
610613
await this.userStore.setMatrixUser(user);
611614
}
612615

616+
public async ensurePasskeyCanDecrypt(): Promise<void> {
617+
if (!this.cryptoStore) {
618+
return;
619+
}
620+
const docs = await this.userStore.select<unknown, {id: string; data: { client_config: ClientConfigMap }}>({
621+
type: "matrix",
622+
"data.client_config": {$exists: true},
623+
});
624+
for (const { id: userId, data } of docs) {
625+
for (const [domain, clientConfig] of Object.entries(data.client_config)) {
626+
if (clientConfig.password) {
627+
try {
628+
this.cryptoStore.decrypt(clientConfig.password);
629+
}
630+
catch (ex) {
631+
log.error(`Failed to decrypt password for ${userId} on ${domain}`, ex);
632+
throw Error('Cannot decrypt user password, refusing to continue', { cause: ex });
633+
}
634+
}
635+
}
636+
}
637+
}
638+
613639
public async getUserFeatures(userId: string): Promise<UserFeatures> {
614640
const matrixUser = await this.userStore.getMatrixUser(userId);
615641
return matrixUser ? (matrixUser.get("features") as UserFeatures || {}) : {};

src/datastore/postgres/PgDataStore.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,13 @@ export class PgDataStore implements DataStore, ProvisioningStore {
516516
const row = res.rows[0];
517517
const config = row.config || {}; // This may not be defined.
518518
if (row.password && this.cryptoStore) {
519-
config.password = this.cryptoStore.decrypt(row.password);
519+
// NOT fatal, but really worrying.
520+
try {
521+
config.password = this.cryptoStore.decrypt(row.password);
522+
}
523+
catch (ex) {
524+
log.warn(`Failed to decrypt password for ${userId} ${domain}`, ex);
525+
}
520526
}
521527
return new IrcClientConfig(userId, domain, config);
522528
}
@@ -545,6 +551,24 @@ export class PgDataStore implements DataStore, ProvisioningStore {
545551
await this.pgPool.query(statement, Object.values(parameters));
546552
}
547553

554+
555+
public async ensurePasskeyCanDecrypt(): Promise<void> {
556+
if (!this.cryptoStore) {
557+
return;
558+
}
559+
const res = await this.pgPool.query<{password: string, user_id: string, domain: string}>(
560+
"SELECT password, user_id, domain FROM client_config WHERE password IS NOT NULL");
561+
for (const { password, user_id, domain } of res.rows) {
562+
try {
563+
this.cryptoStore.decrypt(password);
564+
}
565+
catch (ex) {
566+
log.error(`Failed to decrypt password for ${user_id} on ${domain}`, ex);
567+
throw Error('Cannot decrypt user password, refusing to continue', { cause: ex });
568+
}
569+
}
570+
}
571+
548572
public async getMatrixUserByLocalpart(localpart: string): Promise<MatrixUser|null> {
549573
const res = await this.pgPool.query("SELECT user_id, data FROM matrix_users WHERE user_id = $1", [
550574
`@${localpart}:${this.bridgeDomain}`,

0 commit comments

Comments
 (0)