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:
@@ -25,6 +25,13 @@ vi.mock('@/lib/config', () => ({
|
||||
albumIds: ['00000000-0000-0000-0000-000000000002'],
|
||||
// no password
|
||||
},
|
||||
{
|
||||
// Taken offline with `enabled: false`, and asks for nothing.
|
||||
name: 'Offline',
|
||||
slug: 'offline',
|
||||
albumIds: ['00000000-0000-0000-0000-000000000006'],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
name: 'Hashed',
|
||||
slug: 'hashed',
|
||||
@@ -34,6 +41,13 @@ vi.mock('@/lib/config', () => ({
|
||||
password:
|
||||
'scrypt:0123456789abcdef0123456789abcdef:4850eef18a09612ef11fdcd943bf64ce98d0eca2b0f677f9c0c51d1f648b8a101cf7ea36b6f94cfc05236b2fda6fffabd0929dc7e14c07626522fba68bf26de5',
|
||||
},
|
||||
{
|
||||
// A leftover bcrypt hash: refused outright, whatever the attempt.
|
||||
name: 'Legacy',
|
||||
slug: 'legacy',
|
||||
albumIds: ['00000000-0000-0000-0000-000000000007'],
|
||||
password: '$2b$10$abcdefghijklmnopqrstuuPjJ7x1b0c9vX8rZ0Yx0mZ1n2o3p4q5r',
|
||||
},
|
||||
],
|
||||
}),
|
||||
}));
|
||||
@@ -129,6 +143,41 @@ describe('password hashing does not stall the process', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* A key with an scrypt hash took ~50ms to answer, every other key ~2ms — the
|
||||
* early return skipped hashing. Identical reply bodies did not help: the clock
|
||||
* alone told a prober which pages, albums and journal entries are locked,
|
||||
* drafts included. Every attempt now runs exactly one scrypt. Counting the
|
||||
* calls keeps the test independent of how fast the machine is.
|
||||
*/
|
||||
describe('authenticate — one scrypt per attempt, whatever the key', () => {
|
||||
const attempts: [string, () => Promise<string | null>][] = [
|
||||
['scrypt hash, right password', () => authenticate('hashed', 'scrypted-pass')],
|
||||
['scrypt hash, wrong password', () => authenticate('hashed', 'wrong')],
|
||||
['plaintext, right password', () => authenticate('private', 'secret123')],
|
||||
['plaintext, wrong password', () => authenticate('private', 'wrong')],
|
||||
['key without a password', () => authenticate('public', 'anything')],
|
||||
['unknown key', () => authenticate('no-such-page', 'anything')],
|
||||
['unknown journal entry', () => authenticate('no-such-entry', 'x', 'journal')],
|
||||
['unknown album', () => authenticate('00000000-0000-0000-0000-00000000dead', 'x', 'album')],
|
||||
['legacy bcrypt hash', () => authenticate('legacy', 'anything')],
|
||||
];
|
||||
|
||||
it.each(attempts)('%s', async (_label, attempt) => {
|
||||
const scrypt = vi.spyOn(crypto, 'scrypt');
|
||||
const error = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
try {
|
||||
await attempt();
|
||||
expect(scrypt).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
scrypt.mockRestore();
|
||||
error.mockRestore();
|
||||
warn.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAuthenticated', () => {
|
||||
it('returns true when a valid cookie is present', async () => {
|
||||
// First authenticate to get the expected token
|
||||
@@ -249,6 +298,11 @@ describe('isAlbumReachable', () => {
|
||||
expect(isAlbumReachable(ALSO_STANDALONE, none)).toBe(true);
|
||||
});
|
||||
|
||||
// `enabled: false` takes the subpage offline; its albums have no route left.
|
||||
it('refuses an album whose only subpage is offline', () => {
|
||||
expect(isAlbumReachable('00000000-0000-0000-0000-000000000006', none)).toBe(false);
|
||||
});
|
||||
|
||||
it('still refuses an album that carries its own password', async () => {
|
||||
const subpageOnly = await cookieFor('private', 'secret123');
|
||||
expect(isAlbumReachable(OWN_PASSWORD, subpageOnly)).toBe(false);
|
||||
|
||||
Reference in New Issue
Block a user