Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@fastify/static": "^9.1.3",
"@fastify/swagger": "^9.7.0",
"@fastify/swagger-ui": "^5.2.6",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^12.10.0",
"fastify": "^5.8.5",
"gitsheets": "^1.0.3",
Expand All @@ -34,6 +35,7 @@
},
"devDependencies": {
"@faker-js/faker": "^10.4.0",
"@types/bcryptjs": "^2.4.6",
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^25.8.0",
"msw": "^2.14.6",
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import sessionMiddlewarePlugin from './auth/middleware.js';
import staticWebPlugin from './plugins/static-web.js';
import { healthRoutes } from './routes/health.js';
import { authRoutes } from './routes/auth.js';
import { accountClaimRoutes } from './routes/account-claim.js';
import { projectRoutes } from './routes/projects.js';
import { peopleRoutes } from './routes/people.js';
import { tagRoutes } from './routes/tags.js';
Expand Down Expand Up @@ -141,6 +142,7 @@ export async function buildApp(opts: BuildAppOptions = {}): Promise<FastifyInsta
// ----- 11. Routes -----
await fastify.register(healthRoutes);
await fastify.register(authRoutes);
await fastify.register(accountClaimRoutes);
await fastify.register(projectRoutes);
await fastify.register(peopleRoutes);
await fastify.register(tagRoutes);
Expand Down
26 changes: 26 additions & 0 deletions apps/api/src/auth/legacy-password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Legacy laddr password verification.
*
* Dispatches by hash format prefix so we can support whatever the laddr import
* lands on. v1 supports bcrypt (`$2a$`, `$2b$`, `$2y$`). Other formats throw
* UnknownHashFormatError so callers can surface a uniform "credentials invalid"
* response (rather than leaking algorithm details) while still logging the
* mismatch internally.
*/
import bcrypt from 'bcryptjs';

export class UnknownHashFormatError extends Error {
constructor(prefix: string) {
super(`Unknown legacy password hash format: ${prefix}`);
this.name = 'UnknownHashFormatError';
}
}

const BCRYPT_PREFIXES = ['$2a$', '$2b$', '$2y$'];

export async function verifyLaddrPassword(password: string, hash: string): Promise<boolean> {
if (BCRYPT_PREFIXES.some((p) => hash.startsWith(p))) {
return bcrypt.compare(password, hash);
}
throw new UnknownHashFormatError(hash.slice(0, Math.min(4, hash.length)));
}
6 changes: 5 additions & 1 deletion apps/api/src/plugins/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { HelpWantedWriteService } from '../services/help-wanted.write.js';
import { PersonWriteService } from '../services/person.write.js';
import { TagWriteService } from '../services/tag.write.js';
import { GitHubAccountService } from '../services/github-account.js';
import { AccountClaimService } from '../services/account-claim.js';
import { LoggingNotifier, type Notifier } from '../notify/index.js';

declare module 'fastify' {
Expand All @@ -46,6 +47,7 @@ declare module 'fastify' {
peopleWrite: PersonWriteService;
tagsWrite: TagWriteService;
githubAccount: GitHubAccountService;
accountClaim: AccountClaimService;
};
/** Shared in-memory state — write routes call StateApply.apply against this. */
inMemoryState: InMemoryState;
Expand All @@ -67,6 +69,7 @@ async function servicesPlugin(fastify: FastifyInstance): Promise<void> {
fastify.decorate('fts', fts);
fastify.decorate('notifier', notifier);

const githubAccount = new GitHubAccountService(state);
fastify.decorate('services', {
projects: new ProjectService(state, fts),
people: new PersonService(state, fts),
Expand All @@ -81,7 +84,8 @@ async function servicesPlugin(fastify: FastifyInstance): Promise<void> {
helpWantedWrite: new HelpWantedWriteService(state),
peopleWrite: new PersonWriteService(state, fastify.store.private),
tagsWrite: new TagWriteService(state),
githubAccount: new GitHubAccountService(state),
githubAccount,
accountClaim: new AccountClaimService(state, fastify.store.private, githubAccount),
});
}

Expand Down
Loading