diff --git a/modules/mailu/client.ts b/modules/mailu/client.ts index 08c54b4..4ec4cdb 100644 --- a/modules/mailu/client.ts +++ b/modules/mailu/client.ts @@ -127,8 +127,16 @@ export class MailuClient { } async changePassword(email: string, password: string): Promise { - // enabled: a disabled mailbox is what the provisioner's check reports as lost, so applying the - // mesh's password again also enables it; otherwise the two would disagree for ever. + await this.api("PATCH", `/user/${encodeURIComponent(email)}`, { raw_password: password }); + } + + /** + * Set the mesh's password on a mailbox the mesh provisions, and enable it. A disabled mailbox is + * what the provisioner's check reports as lost, so applying again must enable it, or the two would + * disagree for ever. Separate from changePassword, which an operator's tool uses and which must + * not re-enable a mailbox someone disabled. + */ + async applyProvisioned(email: string, password: string): Promise { await this.api("PATCH", `/user/${encodeURIComponent(email)}`, { raw_password: password, enabled: true }); } diff --git a/modules/mailu/provisioner/index.ts b/modules/mailu/provisioner/index.ts index 41232bb..2b73274 100644 --- a/modules/mailu/provisioner/index.ts +++ b/modules/mailu/provisioner/index.ts @@ -48,7 +48,7 @@ runProvisioner("smtp", { try { await mailu.createUser(email, p.password); } catch { - await mailu.changePassword(email, p.password); + await mailu.applyProvisioned(email, p.password); } }, diff --git a/modules/mssql/client.ts b/modules/mssql/client.ts index e1802a4..e3304ac 100644 --- a/modules/mssql/client.ts +++ b/modules/mssql/client.ts @@ -148,9 +148,17 @@ export class MssqlClient { if (users.length === 0) { await this.exec(`CREATE USER ${ident(login)} FOR LOGIN ${ident(login)}`, database); } else { - // Re-point an existing user at the login. A database restored from elsewhere keeps its user - // under the old login's SID, orphaned; this maps it back, and is a no-op when it already is. - await this.exec(`ALTER USER ${ident(login)} WITH LOGIN = ${ident(login)}`, database); + // Re-point an existing user at the login when its SID is not the login's: a database restored + // from elsewhere keeps its user under the old login's SID, orphaned. Only then, so a user that + // is already mapped is left alone. + const orphaned = await this.query( + `SELECT 1 AS ok FROM sys.database_principals WHERE name = ${literal(login)} ` + + `AND (sid IS NULL OR sid <> SUSER_SID(${literal(login)}))`, + database, + ); + if (orphaned.length > 0) { + await this.exec(`ALTER USER ${ident(login)} WITH LOGIN = ${ident(login)}`, database); + } } await this.exec(`ALTER ROLE db_owner ADD MEMBER ${ident(login)}`, database); }