release: v0.19.0

Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of
[CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md)
for the full list.

Highlights: client proofing links (#683), custom content pages (#738),
asset picker search (#740), concurrent-edit detection (#742), env-locked
settings (#739), an accessibility baseline (#741), three security fixes
with advisories (#737, #755, #759), and a pre-release quality pass
(#780–#797).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ralf <rohlenbo79@gmail.com>
Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com>
Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
This commit is contained in:
Ralksta
2026-09-29 22:50:30 +02:00
committed by GitHub
parent 5f251923f0
commit ad8b09700b
297 changed files with 19398 additions and 1800 deletions

View File

@@ -49,6 +49,29 @@ export async function verifyScrypt(password: string, stored: string): Promise<bo
}
}
/**
* A well-formed hash with a random salt and a random digest, drawn once per
* process. No password matches it (the odds are 2^-512), so verifying against
* it costs exactly what a real verification costs and always fails.
*/
const DUMMY_HASH = `${SCRYPT_PREFIX}${crypto.randomBytes(16).toString('hex')}:${crypto
.randomBytes(64)
.toString('hex')}`;
/**
* Spend one scrypt verification on nothing.
*
* A password check that returns early — no such key, a key without a password,
* a plaintext password compared by HMAC — answers in a couple of milliseconds,
* while a real scrypt hash takes tens. That gap tells anyone who times the
* replies which keys hold a hashed password, even when the reply bodies are
* identical. Callers run this on every path that would otherwise skip scrypt,
* so every attempt costs the same.
*/
export async function burnScrypt(password: string): Promise<void> {
await verifyScrypt(password, DUMMY_HASH);
}
/** Produce a storable `scrypt:salt:hash_hex` string. */
export async function generateScryptHash(password: string): Promise<string> {
const salt = crypto.randomBytes(16).toString('hex');